/*
    File:       AppleCarPlay_AppStub.c
    Package:    Apple CarPlay Communication Plug-in.
    Abstract:   n/a
    Version:    n/a

    Disclaimer: IMPORTANT: This Apple software is supplied to you, by Apple Inc. ("Apple"), in your
    capacity as a current, and in good standing, Licensee in the MFi Licensing Program. Use of this
    Apple software is governed by and subject to the terms and conditions of your MFi License,
    including, but not limited to, the restrictions specified in the provision entitled ”Public
    Software”, and is further subject to your agreement to the following additional terms, and your
    agreement that the use, installation, modification or redistribution of this Apple software
    constitutes acceptance of these additional terms. If you do not agree with these additional terms,
    please do not use, install, modify or redistribute this Apple software.

    Subject to all of these terms and in consideration of your agreement to abide by them, Apple grants
    you, for as long as you are a current and in good-standing MFi Licensee, a personal, non-exclusive
    license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use,
    reproduce, and modify the Apple Software in source form, and to use, reproduce, modify, and
    redistribute the Apple Software, with or without modifications, in binary form. While you may not
    redistribute the Apple Software in source form, should you redistribute the Apple Software in binary
    form, you must retain this notice and the following text and disclaimers in all such redistributions
    of the Apple Software. Neither the name, trademarks, service marks, or logos of Apple Inc. may be
    used to endorse or promote products derived from the Apple Software without specific prior written
    permission from Apple. Except as expressly stated in this notice, no other rights or licenses,
    express or implied, are granted by Apple herein, including but not limited to any patent rights that
    may be infringed by your derivative works or by other works in which the Apple Software may be
    incorporated.

    Unless you explicitly state otherwise, if you provide any ideas, suggestions, recommendations, bug
    fixes or enhancements to Apple in connection with this software (“Feedback”), you hereby grant to
    Apple a non-exclusive, fully paid-up, perpetual, irrevocable, worldwide license to make, use,
    reproduce, incorporate, modify, display, perform, sell, make or have made derivative works of,
    distribute (directly or indirectly) and sublicense, such Feedback in connection with Apple products
    and services. Providing this Feedback is voluntary, but if you do provide Feedback to Apple, you
    acknowledge and agree that Apple may exercise the license granted above without the payment of
    royalties or further consideration to Participant.

    The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR
    IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY
    AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR
    IN COMBINATION WITH YOUR PRODUCTS.

    IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION
    AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
    (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

    Copyright (C) 2007-2021 Apple Inc. All Rights Reserved. Not to be used or disclosed without permission from Apple.
*/

//===========================================================================================================================
//  This file contains sample code snippets that demonstrate how to use the plug-in APIs.
//===========================================================================================================================

#include <stdlib.h>
#include <signal.h>
#include <pthread.h>

#include "StringUtils.h"
#include "AirPlayReceiverServer.h"
#include "AirPlayReceiverSession.h"
#include "AirPlayVersion.h"
#include "AirPlayUtils.h"
#include "DebugServices.h"
#include "HIDKnob.h"
#include "HIDTouchScreen.h"
#include "HIDProximity.h"
#include "HIDTelephony.h"
#include "HIDMediaButtons.h"
#include "MathUtils.h"
#include "AudioUtils.h"
#include "CarPlayLanguages.h"
#include "CarPlayControlClient.h"
#include "BonjourBrowser.h"

#include "iAP2_AppStub.h"
#include "CarPlayInterface.h"

//#include "CIpodConfig.h"


//===========================================================================================================================
//  Internals
//===========================================================================================================================

#define kDefaultUUID            CFSTR( "3a07bd78" )
#define kMainScreenUUID         CFSTR( "e5f7a68d-7b0f-4305-984b-974f677a150b" )
#define kAltScreenUUID          CFSTR( "e5f7a68d-7b0f-4305-984b-b051a776f479" )
#define kAltScreen2UUID         CFSTR( "e5f7a68d-7b0f-4305-984b-b051a776f472" )
#define kScreenNoTimeout        ( (uint64_t) INT64_C( -1 ) )

#define kHsaeVendorId                       0x11c0
#define kHsaeProductId                      0x0001

#define kUSBCountryCodeUnused               0
#define kUSBCountryCodeUS                   33
#define kUSBVendorTouchScreen               kHsaeVendorId
#define kUSBProductTouchScreen              kHsaeProductId
#define kUSBVendorKnobButtons               kHsaeVendorId
#define kUSBProductKnobButtons              kHsaeProductId
#define kUSBVendorProxSensor                0
#define kUSBProductProxSensor               0

#define kUSBVendorTelephony                 kHsaeVendorId
#define kUSBProductTelephony                kHsaeProductId
#define kUSBVendorMediaButtons              kHsaeVendorId
#define kUSBProductMediaButtons             kHsaeProductId

// Prototypes

static OSStatus _SetupUI(void);
static void     _TearDownUI(void);
static void     _RunUI(void);

static void*    _AirPlayThread(void* inArg);

static void     _CarPlayControlClientEventCallback(CarPlayControlClientRef client, CarPlayControlClientEvent event,
                                                   void* eventInfo, void* context);

static CFTypeRef
_AirPlayHandleServerCopyProperty(
    AirPlayReceiverServerRef    inServer,
    CFStringRef                 inProperty,
    CFTypeRef                   inQualifier,
    OSStatus*                   outErr,
    void*                       inContext);

static void
_AirPlayHandleSessionCreated(
    AirPlayReceiverServerRef    inServer,
    AirPlayReceiverSessionRef   inSession,
    void*                       inContext);

static void
_AirPlayHandleSessionFinalized(
    AirPlayReceiverSessionRef inSession,
    void* inContext);

static void
_AirPlayHandleSessionStarted(
    AirPlayReceiverSessionRef inSession,
    void* inContext);

static void
_AirPlayHandleModesChanged(
    AirPlayReceiverSessionRef   inSession,
    const AirPlayModeState*     inState,
    void*                       inContext);

static void
_AirPlayHandleRequestUI(
    AirPlayReceiverSessionRef inSession,
    CFStringRef inURL,
    void* inContext);

static CFTypeRef
_AirPlayHandleSessionCopyProperty(
    AirPlayReceiverSessionRef   inSession,
    CFStringRef                 inProperty,
    CFTypeRef                   inQualifier,
    OSStatus*                   outErr,
    void*                       inContext);

static OSStatus
_AirPlayHandleSessionControl(
    AirPlayReceiverSessionRef   inSession,
    CFStringRef                 inCommand,
    CFTypeRef                   inQualifier,
    CFDictionaryRef             inParams,
    CFDictionaryRef*            outParams,
    void*               inContext);

static void
_AirPlayHandleSetEnhancedSiriParams(
    AirPlayReceiverSessionRef   inSession,
    AirPlayVoiceActivationMode  inMode,
    CFStringRef         inLanguage,
    void*               inContext);

static void
_AirPlayHandleSuggestUI(
    AirPlayReceiverSessionRef   inSession,
    CFArrayRef          inURLs,
    void*               inContext);

static void
_AirPlayHandleDuckAudio(
    AirPlayReceiverSessionRef   inSession,
    double                      inDurationSecs,
    double                      inVolume,
    void*                       inContext);

static void
_AirPlayHandleunDuckAudio(
    AirPlayReceiverSessionRef   inSession,
    double                      inDurationSecs,
    void*                       inContext);

static OSStatus _ParseModes(const char* inArg);
static OSStatus _ParseModeResource(AirPlayResourceChange* inResource, const char* inArg);

static void _initialModesCommand(const char* inCommand);

static void
_sendGenericChangeModeRequest(
    const char*                 inScreenBorrowID,
    AirPlayTransferType         inScreenType,
    AirPlayTransferPriority     inScreenPriority,
    AirPlayConstraint           inScreenTake,
    AirPlayConstraint           inScreenBorrow,
    const char*                 inAudioBorrowID,
    AirPlayTransferType         inAudioType,
    AirPlayTransferPriority     inAudioPriority,
    AirPlayConstraint           inAudioTake,
    AirPlayConstraint           inAudioBorrow,
    AirPlayTriState             inPhone,
    AirPlaySpeechMode           inSpeech,
    AirPlayTriState             inTurnByTurn);

static void
_sendInitialChangeModeRequest(
    const char*                 inScreenBorrowID,
    AirPlayTransferType         inScreenType,
    AirPlayTransferPriority     inScreenPriority,
    AirPlayConstraint           inScreenTake,
    AirPlayConstraint           inScreenBorrow,
    const char*                 inAudioBorrowID,
    AirPlayTransferType         inAudioType,
    AirPlayTransferPriority     inAudioPriority,
    AirPlayConstraint           inAudioTake,
    AirPlayConstraint           inAudioBorrow,
    AirPlayTriState             inPhone,
    AirPlaySpeechMode           inSpeech,
    AirPlayTriState             inTurnByTurn,
    AirPlayEntity               inScreenPermanentEntity,
    AirPlayConstraint           inScreenPermanentTakeConstraint,
    AirPlayConstraint           inScreenPermanentBorrowConstraint,
    AirPlayEntity               inAudioPermanentEntity,
    AirPlayConstraint           inAudioPermanentTakeConstraint,
    AirPlayConstraint           inAudioPermanentBorrowConstraint);

static void
_setChangeModesStruct(
    AirPlayModeChanges*         outModeChanges,
    const char*                 inScreenBorrowID,
    AirPlayTransferType         inScreenType,
    AirPlayTransferPriority     inScreenPriority,
    AirPlayConstraint           inScreenTake,
    AirPlayConstraint           inScreenBorrow,
    const char*                 inAudioBorrowID,
    AirPlayTransferType         inAudioType,
    AirPlayTransferPriority     inAudioPriority,
    AirPlayConstraint           inAudioTake,
    AirPlayConstraint           inAudioBorrow,
    AirPlayTriState             inPhone,
    AirPlaySpeechMode           inSpeech,
    AirPlayTriState             inTurnByTurn);

static void
_changeResourceMode(
    const char*                 inBorrowID,
    AirPlayResourceID           inResourceID,
    AirPlayTransferType         inType,
    AirPlayTransferPriority     inPriority,
    AirPlayConstraint           inTakeConstraint,
    AirPlayConstraint           inBorrowOrUnborrowConstraint);

static void
_changeAppState(
    AirPlaySpeechMode           inSpeechMode,
    AirPlayTriState             inPhoneCall,
    AirPlayTriState             inTurnByTurn);

static void _requestUI();
static void _requestUIWithURL(const char* url);
static void _requestSiriAction(AirPlaySiriAction   inAction);
static void _setNightMode(int mode);
static void _UIAppearanceUpdate(int inMode, int inSetting);
static void _setLimitedUI(int inLimitUI);
static void _AirPlayReceiverSessionEnd();
static void _CarPlayControllerConnect();
static int _CarPlayControllerContainsCString(const char* inDeviceID);
static int  _CarPlayControllerEqual();
static void _AirPlayReceiverSessionSendiAPMessage(int length, const unsigned char* ptr);
static OSStatus _ForceKeyFrame();

static CFArrayRef _getAudioFormats(OSStatus* outErr);
static CFArrayRef _getAudioLatencies(OSStatus* outErr);
static OSStatus _setupHIDDevices(void);
static CFArrayRef _getHIDDevices(OSStatus* outErr);
static CFArrayRef _getScreenDisplays(OSStatus* outErr);
static CFArrayRef _getScreenDisplaysWindowConfig(OSStatus* outErr);
static OSStatus _sendETCUpdate(bool inETCEnabled);

static CFDictionaryRef createInitialModesForBackupCamera(void);
static CFDictionaryRef createInitialModesForController(void);

ulog_define(CarPlayDemoApp, kLogLevelTrace, kLogFlags_Default, "CarPlayDemoApp", NULL);
#define app_ucat()                  &log_category_from_name( CarPlayDemoApp )
#define app_ulog( LEVEL, ... )      ulog( app_ucat(), (LEVEL), __VA_ARGS__ )
#define app_dlog( LEVEL, ... )      dlogc( app_ucat(), (LEVEL), __VA_ARGS__ )

ulog_define(CarPlayDemoAppErr, kLogLevelTrace, kLogFlags_Default | kLogFlags_PrintError, "CarPlayDemoAppErr", NULL);
#define app_ucat_err()                  &log_category_from_name( CarPlayDemoAppErr )
#define app_ulog_err( LEVEL, ... )      ulog( app_ucat_err(), (LEVEL), __VA_ARGS__ )

// Globals

static bool                             gKnob                       = true;
static bool                             gHiFiTouch                  = true;
static bool                             gLoFiTouch                  = false;
static bool                             gTouchpad                   = false;
static bool                             gProxSensor                 = false;
static bool                             gTelephony                  = true;
static bool                             gMediaButtons               = true;
static int                              gPrimaryInputDevice         = kAirPlayDisplayPrimaryInputDeviceTouch;
static bool                             gEnhancedRequestCarUI       = true;
static bool                             gETCSupported               = false;
static AirPlayReceiverServerRef         gAirPlayServer              = NULL;
static AirPlayReceiverSessionRef        gAirPlaySession             = NULL;
static CarPlayControlClientRef          gCarPlayControlClient       = NULL;
static CFMutableArrayRef                gCarPlayControllers         = NULL;
static pthread_t                        gAirPlayThread              = 0;
static AirPlayModeChanges               gInitialModesRaw;
static AirPlayInitialPermanentEntity    gInitialPermanentEntity;
static CFDictionaryRef                  gInitialModes               = NULL;
static bool                             gHasInitialModes            = false;
static CFMutableArrayRef                gBluetoothIDs               = NULL;
static CFBooleanRef                     gRightHandDrive             = NULL;
static CFBooleanRef                     gNightMode                  = NULL;
static CFBooleanRef                     gLimitedUI                  = NULL;
static AirPlayAppearanceMode            gAppearanceMode             = kAirPlayAppearanceMode_Light;
static AirPlayAppearanceSetting         gAppearanceSetting          = kAirPlayAppearanceSetting_Automatic;
static CFStringRef                      gDeviceID                   = NULL;
static CFStringRef                      giOSVersionMin              = NULL;
static CFStringRef                      gOEMIconPath                = NULL;
static CFStringRef                      gOEMIconLabel               = NULL;
static CFBooleanRef                     gOEMIconVisible             = NULL;
static CFStringRef                      gOSInfo               = NULL;


static uint8_t                      gTouchUID;
static uint8_t                      gKnobUID;
static uint8_t                      gProxSensorUID;
static uint8_t                      gTelephonyUID;
static uint8_t                      gMediaButtonsUID;
static uint8_t                      gNextDeviceUID = 0;
static int                          gVideoWidth     = 1024; //162; //1920;
static int                          gVideoHeight    = 600; //95; //720;
static int                          gWidthPhysical  = 162;//= 1024;
static int                          gHeightPhysical = 95;//= 600;


pthread_mutex_t                     authMutex = PTHREAD_MUTEX_INITIALIZER;
char                                gPairingIdentity[ 128 ];

// Current state of the virtual knob
Boolean gSelectButtonPressed = false;
Boolean gHomeButtonPressed = false;
Boolean gBackButtonPressed = false;
Boolean gSiriButtonPressed = false;
double  gXPosition = 0;
double  gYPosition = 0;
int8_t  gWheelPositionRelative = 0;
// Maximum and minimum X values reported by actual knob
#define kMaxXPosition   (1.0)
#define kMinXPosition   (-1.0)
// Maximum and minimum X values reported by actual knob
#define kMaxYPosition   (1.0)
#define kMinYPosition   (-1.0)
//OSStatus    KnobUpdate(void);
OSStatus _KnobUpdate(char selectButtonPressed, char homeButtonPressed, char backButtonPressed, double x,
                     double y, char wheelPositionRelative);


static OSStatus _touchScreenUpdate(uint8_t inPress, uint16_t inX, uint16_t inY);
static OSStatus _telephonyUpdate(int inButtonIndex);
static OSStatus _mediaButtonsUpdate(int inButtonIndex);
static OSStatus _proxSensorUpdate(bool inProxSensorPresence);

static Boolean gSupportsEnhancedSiri = false;
static Boolean gSupportsViewAreas = false;
static Boolean gSupportsUIContext = false;
static Boolean gSupportsAltScreen = false;
static Boolean gSupportsCornerMasks = false;
static Boolean gSupportsFocusTransfer = false;

//===========================================================================================================================
//  setPlatformParam
//===========================================================================================================================

int setPlatformParam(int argc, char** argv) {
    int                 i;
    const char*         arg;
    OSStatus            err;
    for (int i = 0; i < argc; i++) {
        app_ulog(kLogLevelNotice, "AppStub-StartCarplay argc: %s\n", argv[i]);
    }
    // Parse command line arguments.

    for (i = 1; i < argc;) {
        arg = argv[ i++ ];

        if (arg == NULL) {
            break;
        }
        else if (strcmp(arg, "--no-knob") == 0) {
            gKnob = false;
        }
        else if (strcmp(arg, "--no-hi-fi-touch") == 0) {
            gHiFiTouch = false;
        }
        else if (strcmp(arg, "--no-lo-fi-touch") == 0) {
            gLoFiTouch = false;
        }
        else if (strcmp(arg, "--no-touchpad") == 0) {
            gTouchpad = false;
        }
        else if (strcmp(arg, "--no-proxsensor") == 0) {
            gProxSensor = false;
        }
        else if (strcmp(arg, "--btid") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }
            err = CFArrayEnsureCreatedAndAppendCString(&gBluetoothIDs, argv[ i++ ], kSizeCString);
            check_noerr(err);
        }
        else if (strcmp(arg, "--width") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }
            arg = argv[ i++ ];
            gVideoWidth = atoi(arg);
            check_noerr(err);
        }
        else if (strcmp(arg, "--height") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }
            arg = argv[ i++ ];
            gVideoHeight = atoi(arg);

            app_ulog(kLogLevelNotice, "gVideoHeight = %d\n", gVideoHeight);
            check_noerr(err);
        }
        else if (strcmp(arg, "--widthMM") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }
            arg = argv[ i++ ];
            gWidthPhysical = atoi(arg);
            check_noerr(err);
        }
        else if (strcmp(arg, "--heightMM") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }
            arg = argv[ i++ ];
            gHeightPhysical = atoi(arg);
            check_noerr(err);
        }
        else if (strcmp(arg, "--modes") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value. For example:\n", arg);
                fprintf(stderr, "    --modes screen=userInitiated,anytime\n");
                fprintf(stderr, "    --modes mainAudio=anytime,anytime\n");
                fprintf(stderr, "    --modes phoneCall\n");
                fprintf(stderr, "    --modes speech=speaking\n");
                fprintf(stderr, "    --modes turnByTurn\n");
                exit(1);
            }
            arg = argv[ i++ ];
            err = _ParseModes(arg);
            if (err) {
                exit(1);
            }
            gHasInitialModes = true;
        }
        else if (strcmp(arg, "--btid") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }
            err = CFArrayEnsureCreatedAndAppendCString(&gBluetoothIDs, argv[ i++ ], kSizeCString);
            check_noerr(err);
        }
        else if (strcmp(arg, "--right-hand-drive") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }
            arg = argv[ i++ ];
            if (!strcmp(arg, "true")) {
                gRightHandDrive = kCFLBooleanTrue;
            }
            else if (!strcmp(arg, "false")) {
                gRightHandDrive = kCFLBooleanFalse;
            }
            check_noerr(err);
        }
        else if (strcmp(arg, "--enable-etc") == 0) {
            gETCSupported = true;
        }
        else if (strcmp(arg, "--primary-input-device") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }
            arg = argv[ i++ ];
            gPrimaryInputDevice = atoi(arg);
        }
        else if (strcmp(arg, "--enhanced-requestcarui") == 0) {
            gEnhancedRequestCarUI = true;
        }
        else if (strcmp(arg, "--deviceid") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }
            gDeviceID = CFStringCreateWithCString(kCFAllocatorDefault, argv[ i++ ], kCFStringEncodingUTF8);
            check_noerr(err);
        }
        else if (strcmp(arg, "--iOSVersionMin") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }
            giOSVersionMin = CFStringCreateWithCString(kCFAllocatorDefault, argv[ i++ ], kCFStringEncodingUTF8);
            app_ulog(kLogLevelNotice, "AppStub-StartCarplay giOSVersionMin: %@ \n", giOSVersionMin);
            check_noerr(err);
        }
        else if (strcmp(arg, "--oemIconPath") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }

            gOEMIconPath = CFStringCreateWithCString(kCFAllocatorDefault, argv[ i++ ], kCFStringEncodingUTF8);
            app_ulog(kLogLevelNotice, "AppStub-StartCarplay gOEMIconPath: %@ \n", gOEMIconPath);
            check_noerr(err);
        }
        else if (strcmp(arg, "--oemIconLabel") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }

            gOEMIconLabel = CFStringCreateWithCString(kCFAllocatorDefault, argv[ i++ ], kCFStringEncodingUTF8);
            app_ulog(kLogLevelNotice, "AppStub-StartCarplay gOEMIconLabel: %@ \n", gOEMIconLabel);
            check_noerr(err);
        }
        else if (strcmp(arg, "--oemIconVisible") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }

            gOEMIconVisible = kCFBooleanTrue;
            app_ulog(kLogLevelNotice, "AppStub-StartCarplay gOEMIconVisible: %@ \n", gOEMIconVisible);
            check_noerr(err);
        }
        else if (strcmp(arg, "--osinfo") == 0) {
            if (i >= argc) {
                fprintf(stderr, "error: %s requires a value\n", arg);
                exit(1);
            }

            gOSInfo = CFStringCreateWithCString(kCFAllocatorDefault, argv[ i++ ], kCFStringEncodingUTF8);
            app_ulog(kLogLevelNotice, "AppStub-StartCarplay gOSInfo: %@ \n", gOEMIconVisible);
            check_noerr(err);
        }
    }

exit:
    return (err ? 1 : 0);
}

//===========================================================================================================================
//  main
//===========================================================================================================================

//int   main( int argc, char **argv )
int startCarplay(int argc, char** argv) {
    int                 i;
    const char*         arg;
    OSStatus            err;
    char                roleStatus[ 11 ] = { 0 };

    // To disable DAD checking, specify DUPADDRDET 0 on the INTERFACE statement.

//    system("echo 0 > /proc/sys/net/ipv6/conf/usb0/accept_dad");

    // when AppleCarPlay core dump and restart, do it.
//    if (_GetUsbRoleStatus(roleStatus) == 0) {
//        app_ulog(kLogLevelNotice, "Usb RoleStatus %s \n", roleStatus);
//        // usb_device
//        if (strnicmp(roleStatus, "usb_host", 8) == 0) {
//            app_ulog_err(kLogLevelNotice, "Usb role is usb_host, so role switch\n");
//
//            //system("echo usb_otg1_id=0 > /dev/hhhgpio");
//        }
//        else if (strnicmp(roleStatus, "usb_device", 8) == 0) {
//            system("cat /sys/devices/platform/soc/usbc0/usb_host");
//            app_ulog_err(kLogLevelNotice, "Usb role is usb_device, do not role switch\n");
//
//        }
//        else if (strnicmp(roleStatus, "null", 4) == 0) {
//            app_ulog_err(kLogLevelNotice, "Usb role is usb_device, do not role switch\n");
//        }
//    }

    app_ulog(kLogLevelNotice, "AirPlay starting version %s\n", kAirPlaySourceVersionStr);
    signal(SIGPIPE, SIG_IGN);   // Ignore SIGPIPE signals so we get EPIPE errors from APIs instead of a signal.
    AirPlayModeChangesInit(&gInitialModesRaw);
    AirPlayInitialPermanentEntityInit(&gInitialPermanentEntity);
    err = setPlatformParam(argc,argv);
    check_noerr(err);

    if (gHasInitialModes) {
        gInitialModes = AirPlayCreateModesDictionary(&gInitialModesRaw, NULL, &err);
        check_noerr(err);
    }

    // Set up interaction with the platform UI framework.
    err = _SetupUI();
    require_noerr(err, exit);

    err = _setupHIDDevices();
    require_noerr(err, exit);

    // Start AirPlay in a separate thread since the demo app needs to own the main thread.
    app_ulog(kLogLevelNotice, "AppStub-StartCarplay Create AirPlayThread.\n");
    err = pthread_create(&gAirPlayThread, NULL, _AirPlayThread, NULL);
    require_noerr(err, exit);

    // Run the main loop for the app to receive UI events. This doesn't return until the app quits.



    _RunUI();

exit:
    _TearDownUI();
    return (err ? 1 : 0);
}

//===========================================================================================================================
//  _StartAirPlayServer
//===========================================================================================================================

static OSStatus _StartAirPlayServer() {
    OSStatus            err = kNoErr;

    app_ulog(kLogLevelNotice, "_StartAirPlayServer comming.\n");

    if (gAirPlayThread) {
        app_ulog_err(kLogLevelNotice, "AirPlay has been already initialized.\n");
        return kAlreadyInitializedErr;
    }

    // Start AirPlay in a separate thread since the demo app needs to own the main thread.

    err = pthread_create(&gAirPlayThread, NULL, _AirPlayThread, NULL);
    require_noerr(err, exit);

exit:
    if (err)  {
        app_ulog_err(kLogLevelNotice, "Start AirPlay in a separate thread err.\n");
    }
    return (err);
}

//===========================================================================================================================
//  _StopAirPlayServer
//===========================================================================================================================

static void _StopAirPlayServer() {
    if (!gAirPlayThread) {
        app_ulog_err(kLogLevelNotice, "AirPlay has been already stopped err.\n");
        return;
    }
    CFRunLoopStop(CFRunLoopGetCurrent());
    gAirPlayThread = 0;

    app_ulog_err(kLogLevelNotice, "AirPlay is stopped successfully.\n");
}

//===========================================================================================================================
//  _SetupUI
//===========================================================================================================================

static OSStatus _SetupUI(void) {
    OSStatus            err = kNoErr;

    // $$$ TODO: Set up the UI framework application and window for drawing and receiving user events.
    //InitStreamPlayer();

    require_noerr(err, exit);

exit:
    if (err) {
        _TearDownUI();
    }
    return (err);
}

//===========================================================================================================================
//  _TearDownUI
//===========================================================================================================================

static void _TearDownUI(void) {
    // $$$ TODO: Destroy up the UI framework application and window for drawing and receiving user events.
}

//===========================================================================================================================
//  _RunUI
//===========================================================================================================================

static void _RunUI(void) {


    CarPlayInterfaceDelegate    delegate;

    CarPlayInterfaceDelegateInit(&delegate);
    delegate.initialModesCommand_f  = _initialModesCommand;
    delegate.changeMode_f           = _sendGenericChangeModeRequest;
    delegate.initialChangeModes_f   = _sendInitialChangeModeRequest;
    delegate.changeResourceMode_f   = _changeResourceMode;
    delegate.changeAppState_f       = _changeAppState;
    delegate.requestUi_f            = _requestUI;
    delegate.requestUiWithUrl_f     = _requestUIWithURL;
    delegate.requestSiriAction_f    = _requestSiriAction;
    delegate.setNightMode_f         = _setNightMode;
    delegate.uiAppearanceUpdate_f   = _UIAppearanceUpdate;
    delegate.setLimitedUI_f         = _setLimitedUI;
    delegate.touchScreenUpdate_f    = _touchScreenUpdate;
    delegate.telephonyUpdate_f      = _telephonyUpdate;
    delegate.mediaButtonUpdate_f    = _mediaButtonsUpdate;
    delegate.end_f                  = _AirPlayReceiverSessionEnd;
    delegate.forceKeyFrame_f        = _ForceKeyFrame;
    delegate.knob_f                 = _KnobUpdate;

    CarPlayInterfaceSetDelegate(&delegate);

    iAP2InterfaceInitial();

    CarPlay2iAP2InterfaceDelegate   c2iDelegate;

    CarPlay2iAP2InterfaceDelegateInit(&c2iDelegate);
    c2iDelegate.carPlayStartBonjour_f   = _CarPlayControllerConnect;
    c2iDelegate.sendiAPMessage_f        = _AirPlayReceiverSessionSendiAPMessage;
    c2iDelegate.startAirPlayServer_f    = _StartAirPlayServer;
    c2iDelegate.end_f                   = _AirPlayReceiverSessionEnd;
    c2iDelegate.stopAirPlayServer_f     = _StopAirPlayServer;
    c2iDelegate.controllerEqual_f               = _CarPlayControllerEqual;
    CarPlay2iAP2InterfaceSetDelegate(&c2iDelegate);

//  CarPlayInterfaceInitialized();

}

//===========================================================================================================================
//  _CarPlayControllerEqual
//===========================================================================================================================

static int  _CarPlayControllerEqual() {
    CarPlayControllerRef        controller;
    CFIndex                     count;
    uint8_t                     deviceID[ 6 ];
    char                        deviceIDCString[ 18 ];

    app_ulog(kLogLevelNotice, "_CarPlayControllerEqual comming.\n");

    count = CFArrayGetCount(gCarPlayControllers);

    if (count == 0) {
        app_ulog(kLogLevelNotice, "CFMutableArrayRef CarPlayControllers is empty.\n");
        return 0;
    }

    controller = (CarPlayControllerRef) CFArrayGetValueAtIndex(gCarPlayControllers, 0);

    CarPlayControllerGetBluetoothMacAddress(controller, deviceID);
    MACAddressToCString(deviceID, deviceIDCString);

    if (stricmp(gDeviceId, deviceIDCString) != 0) {
        app_ulog_err(kLogLevelNotice, "CarPlayControllerEqual strerr iAP2 deviceId: %s not equal Add/Update deviceID: %s.\n",
                     gDeviceId, deviceIDCString);
        return 0;
    }

    return 1;
}

//===========================================================================================================================
//  _CarPlayControllerConnect
//===========================================================================================================================

static void _CarPlayControllerConnect() {
    CarPlayControllerRef        controller;
    OSStatus                    err;
    CFIndex                     count;
    uint8_t                     deviceID[ 6 ];
    char                     deviceIDCString[ 18 ];

    app_ulog(kLogLevelNotice, "_CarPlayControllerConnect comming. \n");


    count = CFArrayGetCount(gCarPlayControllers);

    if (count == 0) {
        app_ulog_err(kLogLevelNotice, "CarPlayControllerConnect strerr local CarPlayControllers count is 0.\n");
        return;
    }

    controller = (CarPlayControllerRef) CFArrayGetValueAtIndex(gCarPlayControllers, 0);

    CarPlayControllerGetBluetoothMacAddress(controller, deviceID);
    MACAddressToCString(deviceID, deviceIDCString);

    if (stricmp(gDeviceId, deviceIDCString) != 0) {
        app_ulog_err(kLogLevelNotice, "CarPlayControllerConnect strerr iAP2 deviceId: %s not equal Add/Update deviceID: %s.\n",
                     gDeviceId, deviceIDCString);
        return;
    }

    for (int i = 0; i < 5; i++) {
        err = CarPlayControlClientConnect(gCarPlayControlClient, controller);
        app_ulog(kLogLevelNotice, "CarPlayControlClientConnect %s: %#m\n", err ? "failed" : "succeeded", err);

        if (err != kNoErr) {
            sleep(1);
            continue;
        }
        else {
            break;
        }
    }
}

//===========================================================================================================================
//  _CarPlayControlClientEventCallback
//===========================================================================================================================
static void _CarPlayControlClientEventCallback(CarPlayControlClientRef client, CarPlayControlClientEvent event,
                                               void* eventInfo, void* context) {
    (void) client;
    (void) context;
    CarPlayControllerRef controller = (CarPlayControllerRef)eventInfo;
    OSStatus err;

    app_ulog(kLogLevelNotice, "_CarPlayControlClientEventCallback event = %d. \n", event);

    if (event == kCarPlayControlClientEvent_AddOrUpdateController) {
        CFIndex     count, serviceCount;
        uint8_t     deviceID[ 6 ];
        char     deviceIDCString[ 18 ];

        // analyse the CarPlayController

        // deviceID

        CarPlayControllerGetBluetoothMacAddress(controller, deviceID);
        MACAddressToCString(deviceID, deviceIDCString);

        // servicesCount

        CarPlayControllerGetServicesCount(controller, &serviceCount);

        if (serviceCount == 2) {
            app_ulog(kLogLevelNotice, "ignore serviceCount 2 of bonjour.\n");
            return;
        }

        count = CFArrayGetCount(gCarPlayControllers);

        app_ulog(kLogLevelNotice,
                 "CarPlayControlClientEvent Add/Update CarPlayController deviceID: %s, CarPlayControllers Count: %d.\n", deviceIDCString,
                 count);

        // No iAP2 deviceID, iAP2 Authentication may not be complete yet.

        if (strlen(gDeviceId) == 0) {
            app_ulog_err(kLogLevelNotice, "No iAP2 deviceID, iAP2 Authentication may not be complete yet.\n");

            if (count > 0)    {
                CFArrayRemoveAllValues(gCarPlayControllers);
            }
            CFArrayAppendValue(gCarPlayControllers, controller);

            goto exit;
        }

        // With iAP2 deviceID, iAP2 Authentication already is complete. If iAP2 deviceID not equal deviceID, ignore.
        app_ulog(kLogLevelNotice,
                 "CarPlayControlClientEvent Add/Update CarPlayController deviceID: %s, gDeviceId : %s.\n", deviceIDCString,gDeviceId
                );

        if (stricmp(gDeviceId, deviceIDCString) != 0) {
            app_ulog_err(kLogLevelNotice, "iAP2 deviceId: %s not equal Add/Update deviceID: %s, ignore the CarPlayController.\n",
                         gDeviceId, deviceIDCString);
            goto exit;
        }

        // iAP2 deviceID equal deviceID, then update bonjour services.

        if (count > 0)    {
            CFArrayRemoveAllValues(gCarPlayControllers);
        }
        CFArrayAppendValue(gCarPlayControllers, controller);

        app_ulog_err(kLogLevelNotice, "iAP2 deviceID equal deviceID, then update bonjour services.\n");

        BonjourCarPlayAvailability();

    }
    else if (event == kCarPlayControlClientEvent_RemoveController) {
        CFIndex     ndx, count;
        uint8_t     deviceID[ 6 ];
        uint8_t     deviceIDCString[ 18 ];

        count = CFArrayGetCount(gCarPlayControllers);
        ndx = CFArrayGetFirstIndexOfValue(gCarPlayControllers, CFRangeMake(0, count), controller);

        app_ulog_err(kLogLevelNotice, "CarPlayControlClientEvent Remove event received count: %d, ndx: %d\n", count, ndx);

        if (ndx == 0) {
            CFArrayRemoveAllValues(gCarPlayControllers);
        }
    }
    else {
        app_ulog_err(kLogLevelNotice, "CarPlayControlClientEvent event type %d received\n", (int)event);
    }

exit:
    return;
}

//===========================================================================================================================
//  _AirPlayThread
//===========================================================================================================================

static void*    _AirPlayThread(void* inArg) {
    OSStatus                            err;
    AirPlayReceiverServerDelegate       delegate;

    (void) inArg;

    // Create the AirPlay server. This advertise via Bonjour and starts listening for connections.
    err = AirPlayReceiverServerCreate(&gAirPlayServer);
    require_noerr(err, exit);
    app_ulog(kLogLevelNotice, "_AirPlayThread: AirPlayReceiverServerCreate done.\n");

    // Register ourself as a delegate to receive server-level events, such as when a session is created.

    AirPlayReceiverServerDelegateInit(&delegate);
    delegate.copyProperty_f     = _AirPlayHandleServerCopyProperty;
    delegate.sessionCreated_f = _AirPlayHandleSessionCreated;
    AirPlayReceiverServerSetDelegate(gAirPlayServer, &delegate);

    // CarPlay Control

    err = CarPlayControlClientCreateWithServer(&gCarPlayControlClient, gAirPlayServer, _CarPlayControlClientEventCallback,
                                               NULL);
    require_noerr(err, exit);
    app_ulog(kLogLevelNotice, "_AirPlayThread: CarPlayControlClientCreateWithServer done.\n");

    gCarPlayControllers = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    require_action(gCarPlayControllers, exit, err = kNoMemoryErr);
    app_ulog(kLogLevelNotice, "_AirPlayThread: gCarPlayControllers CFArrayCreateMutable done.\n");

    // Start the server and run until the app quits.

    AirPlayReceiverServerStart(gAirPlayServer);
    CarPlayControlClientStart(gCarPlayControlClient);

    CFRunLoopRun();
    CarPlayControlClientStop(gCarPlayControlClient);
    AirPlayReceiverServerStop(gAirPlayServer);

exit:
    CFReleaseNullSafe(gCarPlayControllers);
    CFReleaseNullSafe(gCarPlayControlClient);
    CFReleaseNullSafe(gAirPlayServer);
    return (NULL);
}

//===========================================================================================================================
//  _AirPlayHandleServerCopyProperty
//===========================================================================================================================

static CFTypeRef
_AirPlayHandleServerCopyProperty(
    AirPlayReceiverServerRef    inServer,
    CFStringRef                 inProperty,
    CFTypeRef                   inQualifier,
    OSStatus*                   outErr,
    void*                       inContext) {
    CFTypeRef       value = NULL;
    OSStatus        err;

    (void) inServer;
    (void) inQualifier;
    (void) inContext;

    if (0) {}

    // AudioFormats

    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_AudioFormats))) {
        value = _getAudioFormats(&err);
        require_noerr(err, exit);
        CFRetain(value);
    }

    // AudioLatencies

    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_AudioLatencies))) {
        value = _getAudioLatencies(&err);
        require_noerr(err, exit);
        CFRetain(value);
    }

    // BluetoothIDs

    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_BluetoothIDs))) {
        if (gBluetoothIDs == NULL) {
            char    bluetoothAddress[ 20 ] = { 0 };
            _getBluetoothAddress(bluetoothAddress);

            err = CFArrayEnsureCreatedAndAppendCString(&gBluetoothIDs, bluetoothAddress, strlen(bluetoothAddress));
            require_noerr(err, exit);
        }

        value = gBluetoothIDs;
        require_action_quiet(value, exit, err = kNotHandledErr);
        CFRetain(value);
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayKey_RightHandDrive))) {
        value = gRightHandDrive;
        require_action_quiet(value, exit, err = kNotHandledErr);
        CFRetain(value);
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayKey_NightMode))) {
        value = gNightMode;
        require_action_quiet(value, exit, err = kNotHandledErr);
        CFRetain(value);
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayKey_LimitedUI))) {
        if (!gLimitedUI)  {
            gLimitedUI = kCFLBooleanFalse;
        }
        value = gLimitedUI;
        require_action_quiet(value, exit, err = kNotHandledErr);
        CFRetain(value);
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayKey_LimitedUIElements))) {
        CFMutableArrayRef features;

        features = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
        require_action_quiet(features, exit, err = kNoMemoryErr);

        CFArrayAppendValue(features, CFSTR(kAirPlayLimitedUIElement_SoftKeyboard));
        CFArrayAppendValue(features, CFSTR(kAirPlayLimitedUIElement_SoftPhoneKeypad));
        CFArrayAppendValue(features, CFSTR(kAirPlayLimitedUIElement_NonMusicLists));
        CFArrayAppendValue(features, CFSTR(kAirPlayLimitedUIElement_MusicLists));

        value = features;
        CFRetain(value);
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayKey_VehicleInformation))) {
        CFMutableDictionaryRef vehicleDict;

        vehicleDict = CFDictionaryCreateMutable(NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        require_action_quiet(vehicleDict, exit, err = kNotHandledErr);
        if (gETCSupported) {
            // Support ETC
            CFMutableDictionaryRef dict = NULL;
            dict = CFDictionaryCreateMutable(NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            if (dict) {
                // Start with ETC active
                CFDictionarySetBoolean(dict, CFSTR(kAirPlayKey_Active), true);

                CFDictionarySetValue(vehicleDict, CFSTR(kAirPlayVehicleInformation_ETC), dict);
                ForgetCF(&dict);
                dict = NULL;
            }
        }
        if (CFDictionaryGetCount(vehicleDict) == 0) {
            ForgetCF(&vehicleDict);
            err = kNotHandledErr;
            goto exit;
        }
        else {
            value = vehicleDict;
            CFRetain(value);
        }
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_ExtendedFeatures))) {
        CFMutableArrayRef extendedFeatures;

        extendedFeatures = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
        require_action_quiet(extendedFeatures, exit, err = kNotHandledErr);

        CFArrayAppendValue(extendedFeatures, CFSTR(kAirPlayExtendedFeature_VocoderInfo));

        if (gEnhancedRequestCarUI) {
            CFArrayAppendValue(extendedFeatures, CFSTR(kAirPlayExtendedFeature_EnhancedRequestCarUI));
        }

        if (CFArrayGetCount(extendedFeatures) == 0) {
            ForgetCF(&extendedFeatures);
        }
        else {
            value = extendedFeatures;
            CFRetain(value);
        }
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_OEMIcon))) {
        if (gOEMIconPath != NULL) {
            value = gOEMIconLabel;
            CFRetain(value);
        }
        else {
            err = kNotHandledErr;
            goto exit;
        }
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_OEMIcons))) {
        CFTypeRef obj;
        CFMutableDictionaryRef iconDict;
        CFMutableArrayRef iconsArray;

        iconsArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
        require_action_quiet(iconsArray, exit, err = kNotHandledErr);

        // Add icons for each required size
        obj = CFDataCreateWithFilePath("/app/carplay/res/oemIcons/icon_120x120.png", NULL);
        if (gOEMIconPath != NULL) {
            obj = CFDataCreateWithFilePath(CFStringGetCStringPtr(gOEMIconPath, kCFStringEncodingUTF8), NULL);
        }
        if (obj) {
            iconDict = CFDictionaryCreateMutable(NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            if (iconDict) {
                CFDictionarySetInt64(iconDict, CFSTR(kAirPlayOEMIconKey_WidthPixels), 120);
                CFDictionarySetInt64(iconDict, CFSTR(kAirPlayOEMIconKey_HeightPixels), 120);
                CFDictionarySetBoolean(iconDict, CFSTR(kAirPlayOEMIconKey_Prerendered), true);
                CFDictionarySetValue(iconDict, CFSTR(kAirPlayOEMIconKey_ImageData), obj);
                CFArrayAppendValue(iconsArray, iconDict);
                CFRelease(iconDict);
                iconDict = NULL;
            }
            CFRelease(obj);
        }
        obj = CFDataCreateWithFilePath("/app/carplay/res/oemIcons/icon_180x180.png", NULL);
        if (obj) {
            iconDict = CFDictionaryCreateMutable(NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            if (iconDict) {
                CFDictionarySetInt64(iconDict, CFSTR(kAirPlayOEMIconKey_WidthPixels), 180);
                CFDictionarySetInt64(iconDict, CFSTR(kAirPlayOEMIconKey_HeightPixels), 180);
                CFDictionarySetBoolean(iconDict, CFSTR(kAirPlayOEMIconKey_Prerendered), true);
                CFDictionarySetValue(iconDict, CFSTR(kAirPlayOEMIconKey_ImageData), obj);
                CFArrayAppendValue(iconsArray, iconDict);
                CFRelease(iconDict);
                iconDict = NULL;
            }
            CFRelease(obj);
        }
        obj = CFDataCreateWithFilePath("/app/carplay/res/oemIcons/icon_256x256.png", NULL);
        if (obj) {
            iconDict = CFDictionaryCreateMutable(NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            if (iconDict) {
                CFDictionarySetInt64(iconDict, CFSTR(kAirPlayOEMIconKey_WidthPixels), 256);
                CFDictionarySetInt64(iconDict, CFSTR(kAirPlayOEMIconKey_HeightPixels), 256);
                CFDictionarySetBoolean(iconDict, CFSTR(kAirPlayOEMIconKey_Prerendered), true);
                CFDictionarySetValue(iconDict, CFSTR(kAirPlayOEMIconKey_ImageData), obj);
                CFArrayAppendValue(iconsArray, iconDict);
                CFRelease(iconDict);
                iconDict = NULL;
            }
            CFRelease(obj);
        }
        if (CFArrayGetCount(iconsArray) > 0) {
            value = iconsArray;
            CFRetain(value);
        }
        else {
            CFRelease(iconsArray);
            err = kNotHandledErr;
            goto exit;
        }
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_OEMIconLabel))) {
        if (gOEMIconLabel != NULL) {
            value = gOEMIconLabel;
            CFRetain(value);
        }
        else {
            err = kNotHandledErr;
            goto exit;
        }
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_OEMIconVisible))) {
        if (!gOEMIconVisible) {
            gOEMIconVisible = kCFLBooleanFalse;
        }
        value = gOEMIconVisible;
        require_action_quiet(value, exit, err = kNotHandledErr);
        CFRetain(value);
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_Displays))) {
        value = _getScreenDisplays(&err);
        // value = _getScreenDisplaysWindowConfig( &err );
        require_noerr(err, exit);
        CFRetain(value);
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_HIDDevices))) {
        value = _getHIDDevices(&err);
        require_noerr(err, exit);
        CFRetain(value);
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayKey_ClientOSBuildVersionMin))) {
        if (giOSVersionMin) {
            value = giOSVersionMin; // ie. CFSTR( "11D257" )
            CFRetain(value);
        }
        else {
            err = kNotHandledErr;
            goto exit;
        }
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_DeviceID))) {
        // [ 89911 ] The accessory's Wi-Fi MAC address can be found in ATS > CarPlay session > Info Response > deviceID

        uint8_t     outMACAddress[ 6 ];
        char        MACAddressCString[ 20 ] = { 0 };
        GetInterfaceMACAddress("dumpy", outMACAddress);
        MACAddressToCString(outMACAddress, MACAddressCString);

        gDeviceID = CFStringCreateWithCString(kCFAllocatorDefault, MACAddressCString, kCFStringEncodingUTF8);

        value = gDeviceID;
        require_action_quiet(value, exit, err = kNotHandledErr);
        CFRetain(value);
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayKey_AccessoryEnabledFeatures))) {
        CFMutableArrayRef features;

        features = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
        require_action_quiet(features, exit, err = kNoMemoryErr);

        // Enable if controller (and accessory supports each feature)
        if (gSupportsAltScreen) {
            CFArrayAppendValue(features, CFSTR(kAirPlayKeyAccessoryEnabledFeature_AltScreen));
        }
        if (gSupportsUIContext) {
            CFArrayAppendValue(features, CFSTR(kAirPlayKeyAccessoryEnabledFeature_UIContext));
        }
        if (gSupportsEnhancedSiri) {
            CFArrayAppendValue(features, CFSTR(kAirPlayKeyAccessoryEnabledFeature_EnhancedSiri));
        }
        if (gSupportsViewAreas) {
            CFArrayAppendValue(features, CFSTR(kAirPlayKeyAccessoryEnabledFeature_ViewAreas));
        }

        /*
        if( gSupportsCornerMasks )
            CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_CornerMasks ) );
        */

        /*
        if( gSupportsFocusTransfer )
            CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_FocusTransfer ) );
        */

        value = features;
        CFRetain(value);
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayKey_InfoAltScreenSuggestUIURLs))) {
        if (gSupportsAltScreen) {
            // Include any URLs that you want the device to suggest using suggestUI
            CFMutableArrayRef urls;

            urls = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
            require_action_quiet(urls, exit, err = kNotHandledErr);

            CFArrayAppendValue(urls, CFSTR(kAirPlayKey_ScreenURLsInstrumentClusterMap));
            CFArrayAppendValue(urls, CFSTR(kAirPlayKey_ScreenURLsInstrumentClusterInstructionCard));

            value = urls;
            CFRetain(value);
        }
        else {
            err = kNotHandledErr;
            goto exit;
        }
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayKey_InfoUIContextLastOnDisplayURLs))) {
        if (gSupportsUIContext) {
            // Include any URLs that you want to support
            CFMutableArrayRef urls;

            urls = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
            require_action_quiet(urls, exit, err = kNotHandledErr);

            CFArrayAppendValue(urls, CFSTR(kAirPlayKey_ContextMaps));
            CFArrayAppendValue(urls, CFSTR(kAirPlayKey_ContextPhone));
            CFArrayAppendValue(urls, CFSTR(kAirPlayKey_ContextMessages));
            CFArrayAppendValue(urls, CFSTR(kAirPlayKey_ContextNowPlaying));

            value = urls;
            CFRetain(value);
        }
        else {
            err = kNotHandledErr;
            goto exit;
        }
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayKey_InfoUIContextNowOnDisplayURLs))) {
        if (gSupportsUIContext) {
            // Include any URLs that you want to support
            CFMutableArrayRef urls;

            urls = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
            require_action_quiet(urls, exit, err = kNotHandledErr);

            CFArrayAppendValue(urls, CFSTR(kAirPlayKey_ContextNowPlaying));
            CFArrayAppendValue(urls, CFSTR(kAirPlayKey_ContextMaps));

            value = urls;
            CFRetain(value);
        }
        else {
            err = kNotHandledErr;
            goto exit;
        }
    }
    else if (CFEqual(inProperty, CFSTR(kAirPlayKey_EnhancedSiriInfo))) {
        if (gSupportsEnhancedSiri) {
            CFMutableDictionaryRef dict;
            CFMutableArrayRef languages;

            dict = CFDictionaryCreateMutable(NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            require_action_quiet(dict, exit, err = kNoMemoryErr);

            languages = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
            if (!languages) {
                CFRelease(dict);
                err = kNoMemoryErr;
                goto exit;
            }
            // CFDictionarySetBoolean( dict, CFSTR( kAirPlayKey_EnhancedSiriButton ), true );
            // CFDictionarySetBoolean( dict, CFSTR( kAirPlayKey_EnhancedSiriVoice ), false );
            CFDictionarySetInt64(dict, CFSTR(kAirPlayKey_SupportedSiriTriggerZones), kAirPlaySiriTriggerZone_NotApplicable);
            CFDictionarySetValue(dict, CFSTR(kAirPlayKey_VoiceModelCurrentLanguage), CFSTR(kLanguageRegion_Chinese_MandarinChina));

            CFArrayAppendValue(languages, CFSTR(kLanguageRegion_English_UnitedStates));
            CFArrayAppendValue(languages, CFSTR(kLanguageRegion_Chinese_MandarinChina));
            CFDictionarySetValue(dict, CFSTR(kAirPlayKey_VoiceModelSupportedLanguages), languages);
            CFRelease(languages);
            value = dict;
            CFRetain(dict);
        }
        else {
            err = kNotHandledErr;
            goto exit;
        }
    }
    else {
        err = kNotHandledErr;
        goto exit;
    }
    err = kNoErr;

exit:
    if (outErr) {
        *outErr = err;
    }
    return (value);
}

//===========================================================================================================================
//  _AirPlayReceiverSessionEnd
//===========================================================================================================================

static void _AirPlayReceiverSessionEnd() {
    app_ulog(kLogLevelNotice, "ends the current AirPlay session.\n");

    if (gAirPlaySession)  {
        AirPlayReceiverSessionEnd(gAirPlaySession);
    }
}

//===========================================================================================================================
//  _AirPlayHandleSessionCreated
//===========================================================================================================================

static void
_AirPlayHandleSessionCreated(
    AirPlayReceiverServerRef    inServer,
    AirPlayReceiverSessionRef   inSession,
    void*                       inContext) {
    AirPlayReceiverSessionDelegate      delegate;
    (void) inServer;
    (void) inContext;

    app_ulog_err(kLogLevelNotice, "AirPlay session created.\n");
    gAirPlaySession = inSession;

    // Register ourself as a delegate to receive session-level events, such as modes changes.

    AirPlayReceiverSessionDelegateInit(&delegate);
    delegate.finalize_f     = _AirPlayHandleSessionFinalized;
    delegate.started_f      = _AirPlayHandleSessionStarted;
    delegate.copyProperty_f = _AirPlayHandleSessionCopyProperty;
    delegate.modesChanged_f = _AirPlayHandleModesChanged;
    delegate.requestUI_f    = _AirPlayHandleRequestUI;
    delegate.control_f      = _AirPlayHandleSessionControl;
    delegate.setEnhancedSiriParams_f = _AirPlayHandleSetEnhancedSiriParams;
    delegate.suggestUI_f = _AirPlayHandleSuggestUI;
    delegate.duckAudio_f    = _AirPlayHandleDuckAudio;
    delegate.unduckAudio_f  = _AirPlayHandleunDuckAudio;
    AirPlayReceiverSessionSetDelegate(inSession, &delegate);

    AirPlaySessionCreated();
    handleSessionCreated();

//   gSupportsViewAreas = AirPlayReceiverSessionHasFeatureViewAreas( inSession );
//   gSupportsUIContext = AirPlayReceiverSessionHasFeatureUIContext( inSession );
//   gSupportsEnhancedSiri =  AirPlayReceiverSessionHasFeatureEnhancedSiri( inSession );
//   gSupportsAltScreen = AirPlayReceiverSessionHasFeatureAltScreen( inSession );
//   gSupportsCornerMasks = AirPlayReceiverSessionHasFeatureCornerMasks( inSession );
//   gSupportsFocusTransfer = AirPlayReceiverSessionHasFeatureFocusTransfer( inSession );
}

//===========================================================================================================================
//  _AirPlayHandleSessionFinalized
//===========================================================================================================================

static void _AirPlayHandleSessionFinalized(AirPlayReceiverSessionRef inSession, void* inContext) {
    (void) inSession;
    (void) inContext;

    // Do not delete gCarPlayControllers if support wireless CarPlay.

    gAirPlaySession = NULL;
    app_ulog_err(kLogLevelNotice, "AirPlay session ended.\n");

    handleSessionFinalized();
    AirPlaySessionFinalized();
}

//===========================================================================================================================
//  _GetMACAddressByIP
//===========================================================================================================================

static void _GetMACAddressByIP(const char* inCmd, const char* inIPAddr, char* outMACAddr) {
    FILE*   file;
    char    buffer[ 128 ];
    char*   offset;

    file = popen(inCmd, "r");
    if (!file) {
        app_ulog_err(kLogLevelNotice, "popen %s failed.\n", inCmd);
        goto exit;
    }

    while (fgets(buffer, 128, file)) {
        offset = strstr(buffer, inIPAddr);
        if (offset) {
            offset = strstr(offset, "lladdr");
            strcpy(outMACAddr, offset + 7);
            outMACAddr[ 17 ] = '\0';
        }
    }

exit:
    if (file) {
        pclose(file);
        file = NULL;
    }
}

//===========================================================================================================================
//  _AirPlayHandleSessionStarted
//===========================================================================================================================

static void _AirPlayHandleSessionStarted(AirPlayReceiverSessionRef inSession, void* inContext) {
    OSStatus error;
    CFNumberRef value;
    char    peerAddrCString[ 256 ] = { 0 };
    char    remoteMACAddress[ 256 ];

    (void) inContext;
    app_ulog(kLogLevelNotice, "AirPlay session started.\n");

    // The network interface name that is hosting the session
    CFStringRef ifName = (CFStringRef)AirPlayReceiverSessionCopyProperty(inSession, 0,
                                                                         CFSTR(kAirPlayProperty_InterfaceName), NULL, &error);
    app_ulog(kLogLevelNotice, "The remote IP address of the CarPlay session. \n");
    // The remote IP address of the CarPlay session
    CFStringRef peerAddr = (CFStringRef)AirPlayReceiverSessionCopyProperty(inSession, 0,
                                                                           CFSTR(kAirPlayProperty_PeerIPAddress), NULL, &error);
    app_ulog(kLogLevelNotice, "Start a new iAP2 session over the CarPlay control channel only if the current CarPlay session is over wireless. \n");
    // Start a new iAP2 session over the CarPlay control channel only if the current CarPlay session is over wireless.
    // Disconnecting iAP2 over Bluetooth should only occur when disableBluetooth is received in the AirPlayReceiverSessionControl_f
    // delegate.
    CFNumberRef transportTypeNumber = (CFNumberRef)AirPlayReceiverSessionCopyProperty(inSession, 0,
                                                                                      CFSTR(kAirPlayProperty_TransportType), NULL, &error);


    app_ulog(kLogLevelNotice, "AirPlay session started 1520.\n");
    if (error == kNoErr && transportTypeNumber) {
    	app_ulog(kLogLevelNotice, "AirPlay session started 1522.\n");
        uint32_t transportType;
        CFNumberGetValue((CFNumberRef) transportTypeNumber, kCFNumberSInt32Type, &transportType);
        if (NetTransportTypeIsWiFi(transportType)) {
        	app_ulog(kLogLevelNotice, "AirPlay session started 1526.\n");
            CFStringGetCString(peerAddr, peerAddrCString, 256, kCFStringEncodingUTF8);
            _GetMACAddressByIP("ip neigh", peerAddrCString, remoteMACAddress);
            // This is a CarPlay session over wireless, start iAP2 over CarPlay.  For sending messages use
            // AirPlayReceiverSessionSendiAPMessage() and for receiving message use the kAirPlayCommand_iAPSendMessage command
            // in the AirPlayReceiverSessionControl_f delegate.
            if (giAP2TransportType == kiAP2TransportType_HostUsb) {
                app_ulog_err(kLogLevelNotice,
                             "Dont' Start a new iAP2 session over the CarPlay control channel because transportType: %d.\n", giAP2TransportType);
                gWireConflictWireless = 1;
                app_ulog(kLogLevelNotice, "AirPlay session started 1536.\n");
                return;
            }
            app_ulog(kLogLevelNotice, "AirPlay session started 1539.\n");
            app_ulog(kLogLevelNotice, "Start a new iAP2 session over the CarPlay control channel.\n");
            iAP2OverCarPlaySetup();
            handleSessionStarted(remoteMACAddress);
        }
    }
}

//===========================================================================================================================
//  _AirPlayHandleSessionCopyProperty
//===========================================================================================================================

static CFTypeRef
_AirPlayHandleSessionCopyProperty(
    AirPlayReceiverSessionRef   inSession,
    CFStringRef                 inProperty,
    CFTypeRef                   inQualifier,
    OSStatus*                   outErr,
    void*                       inContext) {
    CFTypeRef       value = NULL;
    OSStatus        err;

    (void) inSession;
    (void) inQualifier;
    (void) inContext;

    // Modes

    if (CFEqual(inProperty, CFSTR(kAirPlayProperty_Modes))) {
        value = createInitialModesForController();
        require_action_quiet(value, exit, err = kNotHandledErr);
        CFRetain(value);
    }
    else {
        err = kNotHandledErr;
        goto exit;
    }
    err = kNoErr;

exit:
    if (outErr) {
        *outErr = err;
    }
    return (value);
}

//===========================================================================================================================
//  _AirPlayHandleModesChanged
//===========================================================================================================================

static void
_AirPlayHandleModesChanged(
    AirPlayReceiverSessionRef   inSession,
    const AirPlayModeState*     inState,
    void*                       inContext) {
    (void) inSession;
    (void) inContext;

    static AirPlayModeState oldState;

    if (oldState.screen        == inState->screen &&
            oldState.permScreen     == inState->permScreen &&
            oldState.mainAudio      == inState->mainAudio &&
            oldState.permMainAudio  == inState->permMainAudio &&
            oldState.speech.entity  == inState->speech.entity &&
            oldState.speech.mode    == inState->speech.mode &&
            oldState.phoneCall      == inState->phoneCall &&
            oldState.turnByTurn     == inState->turnByTurn) {
        return;
    }

    memcpy(&oldState, inState, sizeof(AirPlayModeState));

    app_ulog(kLogLevelNotice,
             "Modes changed: screen %s (perm %s), mainAudio %s (perm %s), speech %s (%s), phone %s, turns %s\n",
             AirPlayEntityToString(inState->screen), AirPlayEntityToString(inState->permScreen),
             AirPlayEntityToString(inState->mainAudio), AirPlayEntityToString(inState->permMainAudio),
             AirPlayEntityToString(inState->speech.entity), AirPlaySpeechModeToString(inState->speech.mode),
             AirPlayEntityToString(inState->phoneCall), AirPlayEntityToString(inState->turnByTurn));

    handleSessionModesChanged(inState->screen, inState->permScreen, inState->mainAudio, inState->permMainAudio,
                              inState->speech.entity, inState->speech.mode, inState->phoneCall, inState->turnByTurn);
}

//===========================================================================================================================
//  _AirPlayHandleRequestUI
//===========================================================================================================================

static void _AirPlayHandleRequestUI(AirPlayReceiverSessionRef inSession, CFStringRef inURL, void* inContext) {
    const char* urlStr = NULL;

    (void) inSession;
    (void) inContext;

    if (inURL) {
        urlStr = CFStringGetCStringPtr(inURL, kCFStringEncodingUTF8);
    }

    app_ulog(kLogLevelNotice, "Request accessory UI: \"%s\"\n", urlStr ? urlStr : "NULL");

    handleSessionRequestUi();
}

static void _AirPlayReceiverSessionSendiAPMessage(int length, const unsigned char* ptr) {
    CFDataRef       data;

    data = CFDataCreate(NULL, ptr, length);

    if (gAirPlaySession)  {
        AirPlayReceiverSessionSendiAPMessage(gAirPlaySession, data, NULL, NULL);
    }
}

//===========================================================================================================================
//  _AirPlayHandleSessionControl
//===========================================================================================================================

static OSStatus
_AirPlayHandleSessionControl(
    AirPlayReceiverSessionRef   inSession,
    CFStringRef                 inCommand,
    CFTypeRef                   inQualifier,
    CFDictionaryRef             inParams,
    CFDictionaryRef*            outParams,
    void*                       inContext) {
    OSStatus err;

    (void) inSession;
    (void) inQualifier;
    (void) inParams;
    (void) outParams;
    (void) inContext;

    if (CFEqual(inCommand, CFSTR(kAirPlayCommand_DisableBluetooth))) {
        char address[64] = { 0 };

        CFDictionaryGetCString(inParams, CFSTR(kAirPlayKey_DeviceID), address, 64, NULL);
        app_ulog(kLogLevelNotice, "Disable Bluetooth session control request MAC address: %s\n", address);
        handleSessionControlDisableBluetooth(address);
        AirPlaySessionControlDisableBluetooth();
        err = kNoErr;
    }
    else if (CFEqual(inCommand, CFSTR(kAirPlayCommand_iAPSendMessage))) {
        CFDataRef data = (CFDataRef)CFDictionaryGetValue(inParams, CFSTR(kAirPlayKey_Data));

        _sendWiFiData((const char*)CFDataGetBytePtr(data), CFDataGetLength(data));
    }
    else if (CFEqual(inCommand, CFSTR(kAirPlayCommand_RequestViewArea))) {
        int index = (int)CFDictionaryGetInt64(inParams, CFSTR(kAirPlayKey_ViewAreaIndex), NULL);
        app_ulog(kLogLevelNotice, "Received RequestViewArea change %u\n", index);
    }
    else if (CFEqual(inCommand, CFSTR(kAirPlayCommand_ChangeUIContext))) {
        CFStringRef url = (CFStringRef)CFDictionaryGetValue(inParams, CFSTR(kAirPlayKey_ContextURL));
        const char* urlStr = NULL;

        if (url) {
            urlStr = CFStringGetCStringPtr(url, kCFStringEncodingUTF8);
            app_ulog(kLogLevelNotice, "Received UI context change for %s\n", urlStr);
        }
    }
    else {
        const char* commond = NULL;

        commond = CFStringGetCStringPtr(inCommand, kCFStringEncodingUTF8);
        app_ulog(kLogLevelNotice, "Unsupported session control request: %s\n", commond);
        err = kNotHandledErr;
    }

    return (err);
}

static void
_AirPlayHandleSetEnhancedSiriParams(
    AirPlayReceiverSessionRef   inSession,
    AirPlayVoiceActivationMode  inMode,
    CFStringRef                 inLanguage,
    void*                       inContext) {
    (void) inMode;
    (void) inLanguage;
    (void) inContext;

    // Start the AuxIn buffering for the plugin
    AirPlayReceiverSessionAuxInStart(inSession, NULL);
}

static void
_AirPlayHandleSuggestUI(
    AirPlayReceiverSessionRef   inSession,
    CFArrayRef          inURLs,
    void*               inContext) {
    CFRange range = CFRangeMake(0, CFArrayGetCount(inURLs));

    if (CFArrayContainsValue(inURLs, range, CFSTR(kAirPlayKey_ScreenURLsInstrumentCluster))) {
        CFStringRef url;
        char compassQuery[32];
        char etaQuery[32];
        char speedLimitQuery[32];

        snprintf(compassQuery, sizeof(compassQuery), "%s=%s", kAirPlayKey_ScreenURLsInstrumentClusterOptionShowCompass, "user");
        snprintf(etaQuery, sizeof(etaQuery), "%s=%s", kAirPlayKey_ScreenURLsInstrumentClusterOptionShowETA, "user");
        snprintf(speedLimitQuery, sizeof(speedLimitQuery), "%s=%s", kAirPlayKey_ScreenURLsInstrumentClusterOptionShowCompass,
                 "user");

        url = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s?%s&%s&%s"), kAirPlayKey_ScreenURLsInstrumentCluster,
                                       compassQuery, etaQuery, speedLimitQuery);
        if (url) {
            AirPlayReceiverSessionShowUI(inSession, kAltScreenUUID, url, NULL, NULL);
            CFRelease(url);
        }
    }
    else {
        AirPlayReceiverSessionStopUI(inSession, kAltScreenUUID, NULL, NULL);
    }
}

static void
_AirPlayHandleDuckAudio(
    AirPlayReceiverSessionRef   inSession,
    double                      inDurationSecs,
    double                      inVolume,
    void*                       inContext) {
    (void) inContext;
    (void) inSession;

    app_ulog(kLogLevelNotice, "AirPlayHandleDuckAudio DurationSecs: %f, Volume: %f.\n", inDurationSecs, inVolume);

    handleSessionDuckAudio(inDurationSecs, inVolume);
}

static void
_AirPlayHandleunDuckAudio(
    AirPlayReceiverSessionRef   inSession,
    double                      inDurationSecs,
    void*                       inContext) {
    (void) inContext;
    (void) inSession;

    app_ulog(kLogLevelNotice, "AirPlayHandleunDuckAudio DurationSecs: %f.\n", inDurationSecs);

    handleSessionUnduckAudio(inDurationSecs);
}

OSStatus _setupHIDDevices(void) {
    // Assign a unique identifier for each HID device
    if (gHiFiTouch || gLoFiTouch) {
        gTouchUID = gNextDeviceUID++;
    }

    if (gKnob) {
        gKnobUID = gNextDeviceUID++;
    }

    if (gProxSensor) {
        gProxSensorUID = gNextDeviceUID++;
    }

    if (gTelephony) {
        gTelephonyUID = gNextDeviceUID++;
    }

    if (gMediaButtons) {
        gMediaButtonsUID = gNextDeviceUID++;
    }

    return (kNoErr);
}

//===========================================================================================================================
//  forceKeyFrame
//===========================================================================================================================

static OSStatus _ForceKeyFrame() {
    OSStatus    err;

    if (gAirPlaySession) {
        app_ulog(kLogLevelNotice, "_forceKeyFrame \n");
        err = AirPlayReceiverSessionForceKeyFrame(gAirPlaySession, kMainScreenUUID, NULL, NULL);
        require_noerr(err, exit);
    }

exit:
    return (err);
}


//===========================================================================================================================
//  _touchScreenUpdate
//===========================================================================================================================

static OSStatus _touchScreenUpdate(uint8_t inPress, uint16_t inX, uint16_t inY) {
    OSStatus        err;
    uint8_t         report[ 5 ];

    app_ulog(kLogLevelNotice, "TouchScreenUpdate Press: %d ( %d, %d )\n", inPress, inX, inY);

    HIDTouchScreenFillReport(report, inPress, inX, inY);

    AirPlayReceiverSessionSendHIDReport(gAirPlaySession, gTouchUID, report, sizeof(report));
    require_noerr(err, exit);

exit:
    return (err);
}

//===========================================================================================================================
//  _telephonyUpdate
//===========================================================================================================================

static OSStatus _telephonyUpdate(int inButtonIndex) {
    app_ulog(kLogLevelNotice, "HIDTelephony buttonIndex: %d\n", inButtonIndex);
    OSStatus        err;
    uint8_t         report[ 1 ];

    HIDTelephonyFillReport(report, inButtonIndex);

    AirPlayReceiverSessionSendHIDReport(gAirPlaySession, gTelephonyUID, report, sizeof(report));
    require_noerr(err, exit);

exit:
    return (err);
}

//===========================================================================================================================
//  _mediaButtonsUpdate
//===========================================================================================================================

static OSStatus _mediaButtonsUpdate(int inButtonIndex) {
    app_ulog(kLogLevelNotice, "mediaButtonsUpdate buttonIndex %d\n", inButtonIndex);
    OSStatus        err;
    uint8_t         report[ 1 ];

    HIDMediaButtonsFillReport(report, inButtonIndex);

    AirPlayReceiverSessionSendHIDReport(gAirPlaySession, gMediaButtonsUID, report, sizeof(report));
    require_noerr(err, exit);

exit:
    if (err != 0) {
        app_ulog(kLogLevelNotice, "send MediaButtons error = %d\n", err);
    }
    return (err);
}

//===========================================================================================================================
//  KnobUpdate
//===========================================================================================================================

static double TranslateValue(double inValue, double inOldMin, double inOldMax, double inNewMin, double inNewMax) {
    return ((((inValue - inOldMin) / (inOldMax - inOldMin)) * (inNewMax - inNewMin)) + inNewMin);
}


OSStatus _KnobUpdate(char selectButtonPressed, char homeButtonPressed, char backButtonPressed, double x,
                     double y, char wheelPositionRelative) {
    OSStatus        err;
    uint8_t         report[ 4 ];
    int8_t          positionX;
    int8_t          positionY;
//
//    // Normalize X and Y values to integers between -127 and 127
    positionX= (int8_t) TranslateValue(x, kMinXPosition, kMaxXPosition, -127, 127);
    positionY = (int8_t) TranslateValue(y, kMinYPosition, kMinYPosition, -127, 127);
//
//    // A HIDKnobFillReport must be sent on both button pressed and button released events
//    HIDKnobFillReport(report, gSelectButtonPressed, gHomeButtonPressed, gBackButtonPressed, x, y, gWheelPositionRelative);

    HIDKnobFillReport(report, selectButtonPressed, homeButtonPressed, backButtonPressed, positionX, positionY,
                      wheelPositionRelative);

    AirPlayReceiverSessionSendHIDReport(gAirPlaySession, gKnobUID, report, sizeof(report));
    require_noerr(err, exit);

exit:
    return (err);
}

//===========================================================================================================================
//  _proxSensorUpdate
//===========================================================================================================================

static OSStatus _proxSensorUpdate(bool inProxSensorPresence) {
    OSStatus        err;
    uint8_t         report[ 1 ];

    HIDProximityFillReport(report, inProxSensorPresence);

    AirPlayReceiverSessionSendHIDReport(gAirPlaySession, gProxSensorUID, report, sizeof(report));
    require_noerr(err, exit);

exit:
    return (err);
}

//===========================================================================================================================
//  _ParseModes
//===========================================================================================================================

static OSStatus _ParseModes(const char* inArg) {
    OSStatus            err;
    const char*         token;
    size_t              len;

    token = inArg;
    while ((*inArg != '\0') && (*inArg != '=')) {
        ++inArg;
    }
    len = (size_t)(inArg - token);
    if (*inArg != '\0') {
        ++inArg;
    }
    if ((strnicmpx(token, len, kAirPlayResourceIDString_MainScreen) == 0) ||
            (strnicmpx(token, len, "screen") == 0)) {
        err = _ParseModeResource(&gInitialModesRaw.screen, inArg);
        require_noerr_quiet(err, exit);
    }
    else if (strnicmpx(token, len, kAirPlayResourceIDString_MainAudio) == 0) {
        err = _ParseModeResource(&gInitialModesRaw.mainAudio, inArg);
        require_noerr_quiet(err, exit);
    }
    else if (strnicmpx(token, len, kAirPlayAppStateIDString_PhoneCall) == 0) {
        gInitialModesRaw.phoneCall = kAirPlayTriState_True;
    }
    else if (strnicmpx(token, len, kAirPlayAppStateIDString_Speech) == 0) {
        if (stricmp(inArg, kAirPlaySpeechModeString_None) == 0) {
            gInitialModesRaw.speech = kAirPlaySpeechMode_None;
        }
        else if (stricmp(inArg, kAirPlaySpeechModeString_Speaking) == 0) {
            gInitialModesRaw.speech = kAirPlaySpeechMode_Speaking;
        }
        else if (stricmp(inArg, kAirPlaySpeechModeString_Recognizing) == 0) {
            gInitialModesRaw.speech = kAirPlaySpeechMode_Recognizing;
        }
        else {
            err = kParamErr;
            goto exit;
        }
    }
    else if (strnicmpx(token, len, kAirPlayAppStateIDString_TurnByTurn) == 0) {
        gInitialModesRaw.turnByTurn = kAirPlayTriState_True;
    }
    else {
        err = kParamErr;
        goto exit;
    }
    err = kNoErr;

exit:
    if (err) {
        fprintf(stderr, "error: bad mode '%s'\n", inArg);
    }
    return (err);
}

//===========================================================================================================================
//  _ParseModeResource
//===========================================================================================================================

static OSStatus _ParseModeResource(AirPlayResourceChange* inResource, const char* inArg) {
    OSStatus            err;
    const char*         token;
    size_t              len;

    inResource->type        = kAirPlayTransferType_Take;
    inResource->priority    = kAirPlayTransferPriority_UserInitiated;

    // borrowID

    token = inArg;
    while ((*inArg != '\0') && (*inArg != ',')) {
        ++inArg;
    }
    len = (size_t)(inArg - token);
    if (*inArg != '\0') {
        ++inArg;
    }
    if (strnicmpx(token, len, kAirPlayBorrowID_NativeVR) == 0) {
        inResource->borrowID = kAirPlayBorrowID_NativeVR;
    }
    else if (strnicmpx(token, len, kAirPlayBorrowID_BackupCamera) == 0) {
        inResource->borrowID = kAirPlayBorrowID_BackupCamera;
    }
    else if (strnicmpx(token, len, kAirPlayBorrowID_PhoneCall) == 0) {
        inResource->borrowID = kAirPlayBorrowID_PhoneCall;
    }
    else if (strnicmpx(token, len, kAirPlayBorrowID_UINotification) == 0) {
        inResource->borrowID = kAirPlayBorrowID_UINotification;
    }
    else if (strnicmpx(token, len, kAirPlayBorrowID_Standby) == 0) {
        inResource->borrowID = kAirPlayBorrowID_Standby;
    }
    else if (strnicmpx(token, len, kAirPlayBorrowID_DisplayOff) == 0) {
        inResource->borrowID = kAirPlayBorrowID_DisplayOff;
    }
    else if (strnicmpx(token, len, kAirPlayBorrowID_AudioOff) == 0) {
        inResource->borrowID = kAirPlayBorrowID_AudioOff;
    }
    else {
        inResource->borrowID = NULL;
    }

    // type

    token = inArg;
    while ((*inArg != '\0') && (*inArg != ',')) {
        ++inArg;
    }
    len = (size_t)(inArg - token);
    if (*inArg != '\0') {
        ++inArg;
    }
    if (strnicmpx(token, len, kAirPlayTransferTypeString_NotApplicable) == 0) {
        inResource->type = kAirPlayTransferType_NotApplicable;
    }
    else if (strnicmpx(token, len, kAirPlayTransferTypeString_Take) == 0) {
        inResource->type = kAirPlayTransferType_Take;
    }
    else if (strnicmpx(token, len, kAirPlayTransferTypeString_Untake) == 0) {
        inResource->type = kAirPlayTransferType_Untake;
    }
    else if (strnicmpx(token, len, kAirPlayTransferTypeString_Borrow) == 0) {
        inResource->type = kAirPlayTransferType_Borrow;
    }
    else if (strnicmpx(token, len, kAirPlayTransferTypeString_Unborrow) == 0) {
        inResource->type = kAirPlayTransferType_Unborrow;
    }
    else {
        err = kParamErr;
        goto exit;
    }

    // priority

    token = inArg;
    while ((*inArg != '\0') && (*inArg != ',')) {
        ++inArg;
    }
    len = (size_t)(inArg - token);
    if (*inArg != '\0') {
        ++inArg;
    }
    if (strnicmpx(token, len, kAirPlayTransferPriorityString_NotApplicable) == 0) {
        inResource->priority = kAirPlayTransferPriority_NotApplicable;
    }
    else if (strnicmpx(token, len, kAirPlayTransferPriorityString_NiceToHave) == 0) {
        inResource->priority = kAirPlayTransferPriority_NiceToHave;
    }
    else if (strnicmpx(token, len, kAirPlayTransferPriorityString_UserInitiated) == 0) {
        inResource->priority = kAirPlayTransferPriority_UserInitiated;
    }
    else {
        err = kParamErr;
        goto exit;
    }

    // TakeConstraint

    token = inArg;
    while ((*inArg != '\0') && (*inArg != ',')) {
        ++inArg;
    }
    len = (size_t)(inArg - token);
    if (*inArg != '\0') {
        ++inArg;
    }
    if (strnicmpx(token, len, kAirPlayConstraintString_Anytime) == 0) {
        inResource->takeConstraint = kAirPlayConstraint_Anytime;
    }
    else if (strnicmpx(token, len, kAirPlayConstraintString_UserInitiated) == 0) {
        inResource->takeConstraint = kAirPlayConstraint_UserInitiated;
    }
    else if (strnicmpx(token, len, kAirPlayConstraintString_Never) == 0) {
        inResource->takeConstraint = kAirPlayConstraint_Never;
    }
    else {
        err = kParamErr;
        goto exit;
    }

    // BorrowConstraint

    token = inArg;
    while ((*inArg != '\0') && (*inArg != ',')) {
        ++inArg;
    }
    len = (size_t)(inArg - token);
    if (*inArg != '\0') {
        ++inArg;
    }
    if (strnicmpx(token, len, kAirPlayConstraintString_Anytime) == 0) {
        inResource->borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;
    }
    else if (strnicmpx(token, len, kAirPlayConstraintString_UserInitiated) == 0) {
        inResource->borrowOrUnborrowConstraint = kAirPlayConstraint_UserInitiated;
    }
    else if (strnicmpx(token, len, kAirPlayConstraintString_Never) == 0) {
        inResource->borrowOrUnborrowConstraint = kAirPlayConstraint_Never;
    }
    else {
        err = kParamErr;
        goto exit;
    }

    err = kNoErr;

exit:
    return (err);
}

//===========================================================================================================================
//  _initialModesCommand
//===========================================================================================================================

static void _initialModesCommand(const char* inCommand) {
    char*               offset = NULL;
    OSStatus            err;

    if (gAirPlaySession) {
        app_ulog_err(kLogLevelNotice, "AirPlaySession has already been started.\n");
        return;
    }

    offset = strnstr(inCommand, "--night", strlen(inCommand));
    if (offset)   {
        gNightMode = kCFBooleanTrue;
    }

    offset = strnstr(inCommand, "screen", strlen(inCommand));
    _ParseModes(offset);

    offset = strnstr(inCommand, "mainAudio", strlen(inCommand));
    _ParseModes(offset);

    offset = strnstr(inCommand, "phoneCall", strlen(inCommand));
    _ParseModes(offset);

    offset = strnstr(inCommand, "speech", strlen(inCommand));
    _ParseModes(offset);

    offset = strnstr(inCommand, "turnByTurn", strlen(inCommand));
    _ParseModes(offset);

    gInitialModes = AirPlayCreateModesDictionary(&gInitialModesRaw, NULL, &err);
    check_noerr(err);

}

//===========================================================================================================================
//  _sendInitialChangeModeRequest
//===========================================================================================================================

static void _sendInitialChangeModeRequest(
    const char*                 inScreenBorrowID,
    AirPlayTransferType         inScreenType,
    AirPlayTransferPriority     inScreenPriority,
    AirPlayConstraint           inScreenTake,
    AirPlayConstraint           inScreenBorrow,
    const char*                 inAudioBorrowID,
    AirPlayTransferType         inAudioType,
    AirPlayTransferPriority     inAudioPriority,
    AirPlayConstraint           inAudioTake,
    AirPlayConstraint           inAudioBorrow,
    AirPlayTriState             inPhone,
    AirPlaySpeechMode           inSpeech,
    AirPlayTriState             inTurnByTurn,
    AirPlayEntity               inScreenPermanentEntity,
    AirPlayConstraint           inScreenPermanentTakeConstraint,
    AirPlayConstraint           inScreenPermanentBorrowConstraint,
    AirPlayEntity               inAudioPermanentEntity,
    AirPlayConstraint           inAudioPermanentTakeConstraint,
    AirPlayConstraint           inAudioPermanentBorrowConstraint) {
    // before the session is active, set initial modes.

    app_ulog(kLogLevelNotice, "\nInitial Mode (car -> iOS):"
             "\n - ScreenBorrowID: %s, ScreenType: %d, ScreenPriority: %d, ScreenTake: %d, ScreenBorrow: %d"
             "\n - AudioBorrowID : %s, AudioType : %d, AudioPriority : %d, AudioTake : %d, AudioBorrow : %d"
             "\n - Phone: %d, Speech: %d, TurnByTurn: %d"
             "\n - ScreenPermanentEntity: %d, ScreenPermanentTakeConstraint: %d, ScreenPermanentBorrowConstraint: %d"
             "\n - AudioPermanentEntity : %d, AudioPermanentTakeConstraint : %d, AudioPermanentBorrowConstraint : %d.\n",
             inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
             inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn,
             inScreenPermanentEntity, inScreenPermanentTakeConstraint, inScreenPermanentBorrowConstraint,
             inAudioPermanentEntity, inAudioPermanentTakeConstraint, inAudioPermanentBorrowConstraint);

    // Initial Mode (car -> iOS)

    _setChangeModesStruct(&gInitialModesRaw, inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
                          inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn);

    gInitialPermanentEntity.permanentEntityScreen = inScreenPermanentEntity;
    gInitialPermanentEntity.screen.takeConstraint = inScreenPermanentTakeConstraint;
    gInitialPermanentEntity.screen.borrowOrUnborrowConstraint = inScreenPermanentBorrowConstraint;

    gInitialPermanentEntity.permanentEntityMainAudio = inAudioPermanentEntity;
    gInitialPermanentEntity.mainAudio.takeConstraint = inAudioPermanentTakeConstraint;
    gInitialPermanentEntity.mainAudio.borrowOrUnborrowConstraint = inAudioPermanentBorrowConstraint;

    gInitialModes = AirPlayCreateInitialModesDictionary(&gInitialPermanentEntity, &gInitialModesRaw, NULL, NULL);
}

//===========================================================================================================================
//  _sendGenericChangeModeRequest
//===========================================================================================================================

static void _sendGenericChangeModeRequest(
    const char*                 inScreenBorrowID,
    AirPlayTransferType         inScreenType,
    AirPlayTransferPriority     inScreenPriority,
    AirPlayConstraint           inScreenTake,
    AirPlayConstraint           inScreenBorrow,
    const char*                 inAudioBorrowID,
    AirPlayTransferType         inAudioType,
    AirPlayTransferPriority     inAudioPriority,
    AirPlayConstraint           inAudioTake,
    AirPlayConstraint           inAudioBorrow,
    AirPlayTriState             inPhone,
    AirPlaySpeechMode           inSpeech,
    AirPlayTriState             inTurnByTurn) {
    AirPlayModeChanges      changes;

    //when the Airplay session is active
    if (gAirPlaySession) {
        app_ulog(kLogLevelNotice, "\nChange Mode (car -> iOS):"
                 "\n - ScreenBorrowID: %s, ScreenType: %d, ScreenPriority: %d, ScreenTake: %d, ScreenBorrow: %d"
                 "\n - AudioBorrowID : %s, AudioType : %d, AudioPriority : %d, AudioTake : %d, AudioBorrow : %d"
                 "\n - Phone: %d, Speech: %d, TurnByTurn: %d.\n", inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake,
                 inScreenBorrow, inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech,
                 inTurnByTurn);
        // changeModes (car -> iOS)
        _setChangeModesStruct(&changes, inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
                              inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn);
        AirPlayReceiverSessionChangeModes(gAirPlaySession, &changes, NULL, NULL, NULL);
    }

}

//===========================================================================================================================
//  _setChangeModesStruct
//===========================================================================================================================

static void _setChangeModesStruct(
    AirPlayModeChanges*         outModeChanges,
    const char*                 inScreenBorrowID,
    AirPlayTransferType         inScreenType,
    AirPlayTransferPriority     inScreenPriority,
    AirPlayConstraint           inScreenTake,
    AirPlayConstraint           inScreenBorrow,
    const char*                 inAudioBorrowID,
    AirPlayTransferType         inAudioType,
    AirPlayTransferPriority     inAudioPriority,
    AirPlayConstraint           inAudioTake,
    AirPlayConstraint           inAudioBorrow,
    AirPlayTriState             inPhone,
    AirPlaySpeechMode           inSpeech,
    AirPlayTriState             inTurnByTurn) {
    if (!outModeChanges) {
        return;
    }

    outModeChanges->screen.borrowID                         = inScreenBorrowID;
    outModeChanges->screen.type                             = inScreenType;
    outModeChanges->screen.priority                         = inScreenPriority;
    outModeChanges->screen.takeConstraint                   = inScreenTake;
    outModeChanges->screen.borrowOrUnborrowConstraint       = inScreenBorrow;
    outModeChanges->mainAudio.borrowID                      = inAudioBorrowID;
    outModeChanges->mainAudio.type                          = inAudioType;
    outModeChanges->mainAudio.priority                      = inAudioPriority;
    outModeChanges->mainAudio.takeConstraint                = inAudioTake;
    outModeChanges->mainAudio.borrowOrUnborrowConstraint    = inAudioBorrow;
    outModeChanges->phoneCall                               = inPhone;
    outModeChanges->speech                                  = inSpeech;
    outModeChanges->turnByTurn                              = inTurnByTurn;
}

//===========================================================================================================================
//  _changeResourceMode
//===========================================================================================================================

static void _changeResourceMode(
    const char*                                 inBorrowID,
    AirPlayResourceID                           inResourceID,
    AirPlayTransferType                         inType,
    AirPlayTransferPriority                     inPriority,
    AirPlayConstraint                           inTakeConstraint,
    AirPlayConstraint                           inBorrowOrUnborrowConstraint) {
    OSStatus    err;
    CFStringRef borrowID = NULL;

    app_ulog(kLogLevelNotice, "\nChange Resource Mode (car -> iOS):"
             "\n - ResourceID: %d, BorrowID: %s, Type: %d, Priority: %d, TakeConstraint: %d, BorrowOrUnborrowConstraint: %d\n",
             inResourceID, inBorrowID, inType, inPriority, inTakeConstraint, inBorrowOrUnborrowConstraint);

    borrowID = CFStringCreateWithCString(kCFAllocatorDefault, inBorrowID, kCFStringEncodingUTF8);
    require_action(borrowID, exit, err = kNoMemoryErr);

    err = AirPlayReceiverSessionChangeResourceMode(gAirPlaySession, inResourceID, inType,
                                                   inPriority, inTakeConstraint, inBorrowOrUnborrowConstraint, borrowID, NULL, NULL, NULL);
    require_noerr(err, exit);

exit:
    CFReleaseNullSafe(borrowID);
}

//===========================================================================================================================
//  _changeAppState
//===========================================================================================================================

static void _changeAppState(
    AirPlaySpeechMode                           inSpeechMode,
    AirPlayTriState                             inPhoneCall,
    AirPlayTriState                             inTurnByTurn) {
    app_ulog(kLogLevelNotice, "\nChange App Mode (car -> iOS):"
             "\n - Phone: %d, Speech: %d, TurnByTurn: %d.\n", inPhoneCall, inSpeechMode, inTurnByTurn);

    if (gAirPlaySession) {
        AirPlayReceiverSessionChangeAppState(gAirPlaySession, inSpeechMode, inPhoneCall, inTurnByTurn, NULL, NULL, NULL);
    }
}

//===========================================================================================================================
//  _requestUI
//===========================================================================================================================

static void _requestUI() {
    app_ulog(kLogLevelNotice, "requestUI.\n");

    if (gAirPlaySession) {
        AirPlayReceiverSessionRequestUI(gAirPlaySession, NULL, NULL, NULL);
    }
}

static void _requestUIWithURL(const char* url) {
    app_ulog(kLogLevelNotice, "requestUI with URL %s\n", url);

    if (gAirPlaySession) {
        AirPlayReceiverSessionRequestUI(gAirPlaySession, CFStringCreateWithCString(kCFAllocatorDefault, url,
                                                                                   kCFStringEncodingUTF8), NULL, NULL);
    }
}

//===========================================================================================================================
//  _requestSiriAction
//===========================================================================================================================

static void _requestSiriAction(AirPlaySiriAction   inAction) {
    app_ulog(kLogLevelNotice, "requestSiriAction Action: %d\n", inAction);

    if (gAirPlaySession) {
        AirPlayReceiverSessionRequestSiriAction(gAirPlaySession, inAction, NULL, NULL);
    }
}

//===========================================================================================================================
//  _setNightMode
//===========================================================================================================================

static void _setNightMode(int mode) {
    app_ulog(kLogLevelNotice, "set night mode: %d.\n", mode);

    if (mode == 1) {
        gNightMode = kCFLBooleanTrue;
    }
    if (mode == 0) {
        gNightMode = kCFLBooleanFalse;
    }

    if (gAirPlaySession) {
        AirPlayReceiverSessionSetNightMode(gAirPlaySession, mode, NULL, NULL);
    }
}

//===========================================================================================================================
//  _sendUIAppearanceUpdate
//===========================================================================================================================

static void _UIAppearanceUpdate(int inMode, int inSetting) {
    app_ulog(kLogLevelNotice, "sends an UI appearance update mode: %d, setting: %d.\n", inMode, inSetting);

    gAppearanceMode = inMode;

    if (inSetting) {
        gAppearanceSetting = kAirPlayAppearanceSetting_Automatic;
    }
    else {
        gAppearanceSetting = kAirPlayAppearanceSetting_UserChoice;
    }

    if (gAirPlaySession) {
        AirPlayReceiverSessionUIAppearanceUpdate(gAirPlaySession, kMainScreenUUID, inMode, gAppearanceSetting, NULL, NULL);
    }
}

//===========================================================================================================================
//  _setLimitedUI
//===========================================================================================================================

static void _setLimitedUI(int inLimitUI) {
    app_ulog(kLogLevelNotice, "set limited UI: %d.\n", inLimitUI);

    if (inLimitUI == 1) {
        gLimitedUI = kCFLBooleanTrue;
    }
    if (inLimitUI == 0) {
        gLimitedUI = kCFLBooleanFalse;
    }

    if (gAirPlaySession) {
        AirPlayReceiverSessionSetLimitedUI(gAirPlaySession, inLimitUI, NULL, NULL);
    }
}

//===========================================================================================================================
//  _sendETCUpdate
//===========================================================================================================================

static OSStatus _sendETCUpdate(bool inETCEnabled) {
    OSStatus err;
    CFMutableDictionaryRef etcDict = NULL;
    CFMutableDictionaryRef vehicleDict = NULL;

    etcDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    require_action(etcDict, exit, err = kNoMemoryErr);
    CFDictionarySetBoolean(etcDict, CFSTR(kAirPlayKey_Active), inETCEnabled);

    vehicleDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    require_action(vehicleDict, exit, err = kNoMemoryErr);
    CFDictionarySetValue(vehicleDict, CFSTR(kAirPlayVehicleInformation_ETC), etcDict);

    err = AirPlayReceiverSessionUpdateVehicleInformation(gAirPlaySession, vehicleDict, NULL, NULL);
    require_noerr(err, exit);

exit:
    CFReleaseNullSafe(etcDict);
    CFReleaseNullSafe(vehicleDict);
    return (err);
}

//===========================================================================================================================
//  _getAudioFormats
//===========================================================================================================================

static CFArrayRef _getAudioFormats(OSStatus* outErr) {
    CFArrayRef                  dictArray = NULL;
    OSStatus                    err;
    AirPlayAudioFormat          inputFormats, outputFormats;

    // Main Audio - Compatibility
    inputFormats =
        kAirPlayAudioFormat_PCM_8KHz_16Bit_Mono |
        kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
        kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono;
    outputFormats =
        kAirPlayAudioFormat_PCM_8KHz_16Bit_Mono |
        kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
        kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
        // kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
        kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo;
    err = AirPlayInfoArrayAddAudioFormat(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Compatibility,
                                         inputFormats, outputFormats);
    require_noerr(err, exit);

//    // Alt Audio - Compatibility
//  inputFormats = 0;
//  outputFormats =
////         kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
//      kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo;
//  err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Compatibility, inputFormats, outputFormats );
//  require_noerr( err, exit );

    // Main Audio - Alert
    inputFormats = 0;
    outputFormats =
        //kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
        kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo |
        //kAirPlayAudioFormat_OPUS_48KHz_Mono |
        //kAirPlayAudioFormat_AAC_ELD_48KHz_Mono |
        //kAirPlayAudioFormat_AAC_ELD_44KHz_Mono |
        //kAirPlayAudioFormat_AAC_ELD_48KHz_Stereo;
        kAirPlayAudioFormat_AAC_ELD_44KHz_Stereo;
    err = AirPlayInfoArrayAddAudioFormat(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Alert, inputFormats,
                                         outputFormats);
    require_noerr(err, exit);

    // Main Audio - Default
    inputFormats =
        kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
        kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
        //kAirPlayAudioFormat_OPUS_24KHz_Mono |
        //kAirPlayAudioFormat_OPUS_16KHz_Mono;
        kAirPlayAudioFormat_AAC_ELD_16KHz_Mono |
        kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
    outputFormats =
        kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
        kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
        //kAirPlayAudioFormat_OPUS_24KHz_Mono |
        //kAirPlayAudioFormat_OPUS_16KHz_Mono;
        kAirPlayAudioFormat_AAC_ELD_16KHz_Mono |
        kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
    err = AirPlayInfoArrayAddAudioFormat(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Default,
                                         inputFormats, outputFormats);
    require_noerr(err, exit);

    // Main Audio - Media
    inputFormats = 0;
    outputFormats =
        //kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
        kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo;
    err = AirPlayInfoArrayAddAudioFormat(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Media, inputFormats,
                                         outputFormats);
    require_noerr(err, exit);

    // Main Audio - Telephony
    inputFormats =
        kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
        kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
        //kAirPlayAudioFormat_PCM_32KHz_16Bit_Mono |
        //kAirPlayAudioFormat_OPUS_16KHz_Mono |
        //kAirPlayAudioFormat_OPUS_24KHz_Mono |
        //kAirPlayAudioFormat_OPUS_48KHz_Mono;
        kAirPlayAudioFormat_AAC_ELD_16KHz_Mono |
        kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
    //kAirPlayAudioFormat_AAC_ELD_32KHz_Mono;
    outputFormats =
        kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
        kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
        //kAirPlayAudioFormat_PCM_32KHz_16Bit_Mono |
        //kAirPlayAudioFormat_OPUS_16KHz_Mono |
        //kAirPlayAudioFormat_OPUS_24KHz_Mono |
        //kAirPlayAudioFormat_OPUS_48KHz_Mono;
        kAirPlayAudioFormat_AAC_ELD_16KHz_Mono |
        kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
    //kAirPlayAudioFormat_AAC_ELD_32KHz_Mono;
    err = AirPlayInfoArrayAddAudioFormat(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Telephony,
                                         inputFormats, outputFormats);
    require_noerr(err, exit);


    // Main Audio - SpeechRecognition
    inputFormats =
        kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
        //kAirPlayAudioFormat_OPUS_24KHz_Mono |
        kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
    outputFormats =
        kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
        //kAirPlayAudioFormat_OPUS_24KHz_Mono |
        kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
    err = AirPlayInfoArrayAddAudioFormat(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_SpeechRecognition,
                                         inputFormats, outputFormats);
    require_noerr(err, exit);

    // Alt Audio - Default
    inputFormats = 0;
    outputFormats =
        //kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
        kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo |
        //kAirPlayAudioFormat_OPUS_48KHz_Mono |
        //kAirPlayAudioFormat_AAC_ELD_48KHz_Mono |
        //kAirPlayAudioFormat_AAC_ELD_44KHz_Mono |
        kAirPlayAudioFormat_AAC_ELD_48KHz_Stereo;
    //kAirPlayAudioFormat_AAC_ELD_44KHz_Stereo;
    err = AirPlayInfoArrayAddAudioFormat(&dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Default, inputFormats,
                                         outputFormats);
    require_noerr(err, exit);

    // Alt Audio - Compatibility
    inputFormats = 0;
    outputFormats =
        // kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
        kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo;
    err = AirPlayInfoArrayAddAudioFormat(&dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Compatibility,
                                         inputFormats, outputFormats);
    require_noerr(err, exit);

    // Main High - Media
    inputFormats = 0;
    outputFormats =
        kAirPlayAudioFormat_AAC_LC_48KHz_Stereo;
    // kAirPlayAudioFormat_AAC_LC_44KHz_Stereo;
    err = AirPlayInfoArrayAddAudioFormat(&dictArray, kAudioStreamType_MainHighAudio, kAudioStreamAudioType_Media,
                                         inputFormats, outputFormats);
    require_noerr(err, exit);

    if (gSupportsEnhancedSiri) {
        // Aux Out - speechRecognition
        inputFormats = 0;
        outputFormats =
            kAirPlayAudioFormat_PCM_48KHz_16Bit_Mono;
        //kAirPlayAudioFormat_OPUS_48KHz_Mono;
        err = AirPlayInfoArrayAddAudioFormat(&dictArray, kAudioStreamType_AuxOutAudio, kAudioStreamAudioType_SpeechRecognition,
                                             inputFormats, outputFormats);
        require_noerr(err, exit);

        // Aux In - speechRecognition
        outputFormats = 0;
        inputFormats =
            kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono;
        //kAirPlayAudioFormat_OPUS_16KHz_Mono;
        err = AirPlayInfoArrayAddAudioFormat(&dictArray, kAudioStreamType_AuxInAudio, kAudioStreamAudioType_SpeechRecognition,
                                             inputFormats, outputFormats);
        require_noerr(err, exit);
    }

exit:
    if (outErr) {
        *outErr = err;
    }
    return (dictArray);
}

//===========================================================================================================================
//  _getAudioLatencies
//===========================================================================================================================

static CFArrayRef _getAudioLatencies(OSStatus* outErr) {
    CFArrayRef                          dictArray = NULL;
    OSStatus                            err;

    // $$$ TODO: obtain audio latencies for all audio formats and audio types supported by the underlying hardware.
    // Audio latencies are reported as an ordered array of dictionaries (from least restrictive to the most restrictive).
    // Each dictionary contains the following keys:
    //      [kAudioSessionKey_Type] - if not specified, then latencies are good for all stream types
    //      [kAudioSessionKey_AudioType] - if not specified, then latencies are good for all audio types
    //      [kAudioSessionKey_SampleRate] - if not specified, then latencies are good for all sample rates
    //      [kAudioSessionKey_SampleSize] - if not specified, then latencies are good for all sample sizes
    //      [kAudioSessionKey_Channels] - if not specified, then latencies are good for all channel counts
    //      [kAudioSessionKey_CompressionType] - if not specified, then latencies are good for all compression types
    //      kAudioSessionKey_InputLatencyMicros
    //      kAudioSessionKey_OutputLatencyMicros

    // MainAudio catch all - set 0 latencies for now - $$$ TODO set real latencies
    // err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, NULL, 0, 0, 0, 0, 0 );
    // require_noerr( err, exit );

    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Compatibility, 8000,
                                          16, 1, 160000, 40000);
    require_noerr(err, exit);

    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Compatibility,
                                          16000, 16, 1, 160000, 40000);
    require_noerr(err, exit);

    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Compatibility,
                                          24000, 16, 1, 160000, 40000);
    require_noerr(err, exit);

    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Compatibility,
                                          48000, 16, 2, 0, 40000);
    require_noerr(err, exit);

    // MainAudio default latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Default, 16000, 16,
                                          1, 160000, 40000);
    require_noerr(err, exit);

    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Default, 24000, 16,
                                          1, 160000, 40000);
    require_noerr(err, exit);

    // MainAudio media latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Media, 48000, 16, 2,
                                          0, 40000);
    require_noerr(err, exit);

    // MainAudio telephony latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Telephony, 16000,
                                          16, 1, 160000, 40000);
    require_noerr(err, exit);

    // MainAudio telephony latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Telephony, 24000,
                                          16, 1, 160000, 40000);
    require_noerr(err, exit);

    // MainAudio SpeechRecognition latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_SpeechRecognition,
                                          24000, 16, 1, 160000, 40000);
    require_noerr(err, exit);

    // Main Audio alert latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Alert, 48000, 16, 2,
                                          0, 40000);
    require_noerr(err, exit);

    // AltAudio catch all latencies - set 0 latencies for now - $$$ TODO set real latencies
    // err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_AltAudio, NULL, 0, 0, 0, 0, 0 );
    // require_noerr( err, exit );

    // AltAudio default latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Default, 48000, 16,
                                          2, 0, 40000);
    require_noerr(err, exit);

    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Compatibility, 48000,
                                          16, 2, 0, 40000);
    require_noerr(err, exit);

    // MainHighAudio Media latencies (wireless only) - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency(&dictArray, kAudioStreamType_MainHighAudio, kAudioStreamAudioType_Media, 48000,
                                          16, 2, 0, 40000);
    require_noerr(err, exit);

    // $$$ TODO add more latencies dictionaries as needed

exit:
    if (outErr) {
        *outErr = kNoErr;
    }
    return (dictArray);
}

//===========================================================================================================================
//  GetHIDDevices
//===========================================================================================================================

static CFArrayRef _getHIDDevices(OSStatus* outErr) {
    CFArrayRef                  dictArray = NULL;
    OSStatus                    err;

    if (gHiFiTouch || gLoFiTouch) {
        uint8_t*    descPtr;
        size_t      descLen;

        err = HIDTouchScreenSingleCreateDescriptor(&descPtr, &descLen, gVideoWidth, gVideoHeight);
        require_noerr(err, exit);
        err = AirPlayInfoArrayAddHIDDevice(&dictArray, gTouchUID, "Touch Screen", kUSBVendorTouchScreen, kUSBProductTouchScreen,
                                           kUSBCountryCodeUnused, descPtr, descLen, kMainScreenUUID);
        free(descPtr);
        require_noerr(err, exit);
    }

    if (gKnob) {
        uint8_t*    descPtr;
        size_t      descLen;

        err = HIDKnobCreateDescriptor(&descPtr, &descLen);
        require_noerr(err, exit);
        err = AirPlayInfoArrayAddHIDDevice(&dictArray, gKnobUID, "Knob Buttons", kUSBVendorKnobButtons, kUSBProductKnobButtons,
                                           kUSBCountryCodeUnused, descPtr, descLen, kMainScreenUUID);
        free(descPtr);
        require_noerr(err, exit);
    }

    if (gProxSensor) {
        uint8_t*    descPtr;
        size_t      descLen;

        err = HIDProximityCreateDescriptor(&descPtr, &descLen);
        require_noerr(err, exit);
        err = AirPlayInfoArrayAddHIDDevice(&dictArray, gProxSensorUID, "Proximity Sensor", kUSBVendorProxSensor,
                                           kUSBProductProxSensor, kUSBCountryCodeUnused, descPtr, descLen, kMainScreenUUID);
        free(descPtr);
        require_noerr(err, exit);
    }

    if (gTelephony) {
        uint8_t*    descPtr;
        size_t      descLen;

        err = HIDTelephonyCreateDescriptor(&descPtr, &descLen);
        require_noerr(err, exit);
        err = AirPlayInfoArrayAddHIDDevice(&dictArray, gTelephonyUID, "Telephony Key", kUSBVendorTelephony,
                                           kUSBProductTelephony, kUSBCountryCodeUS, descPtr, descLen, kDefaultUUID);
        free(descPtr);
        require_noerr(err, exit);
    }

    if (gMediaButtons) {
        uint8_t*    descPtr;
        size_t      descLen;

        err = HIDMediaButtonsCreateDescriptor(&descPtr, &descLen);
        require_noerr(err, exit);
        err = AirPlayInfoArrayAddHIDDevice(&dictArray, gMediaButtonsUID, "Media Buttons", kUSBVendorMediaButtons,
                                           kUSBProductMediaButtons, kUSBCountryCodeUS, descPtr, descLen, kDefaultUUID);
        free(descPtr);
        require_noerr(err, exit);
    }

    err = kNoErr;

exit:
    if (outErr) {
        *outErr = err;
    }
    return (dictArray);
}

//===========================================================================================================================
//  _getScreenDisplays
//===========================================================================================================================

static CFArrayRef _getScreenDisplays(OSStatus* outErr) {
    OSStatus err;

    uint32_t mainWidthPixels = gVideoWidth;
    uint32_t mainHeightPixels = gVideoHeight;
    uint32_t mainWidthMM = gWidthPhysical;
    uint32_t mainHeightMM = gHeightPhysical;
    uint32_t mainFPS = 30;

    uint32_t altWidthPixels = 800;
    uint32_t altHeightPixels = 480;
    uint32_t altWidthMM = 175;
    uint32_t altHeightMM = 105;
    uint32_t altFPS = 30;

    CFArrayRef dictArray = NULL;
    CFMutableDictionaryRef screenDict = NULL;
    uint32_t u32 = 0;

    uint32_t viewAreaCount = 4;
    uint32_t initialViewArea = 0;
    AirPlayScreenArea activeAreas[4];
    uint32_t adjacentAreas[2];
    uint32_t adjacentAreaCount;

    if (gKnob) {
        u32 |= kAirPlayDisplayFeatures_Knobs;
    }
    if (gHiFiTouch)   {
        u32 |= kAirPlayDisplayFeatures_HighFidelityTouch;
    }
    if (gLoFiTouch)   {
        u32 |= kAirPlayDisplayFeatures_LowFidelityTouch;
    }
    if (gTouchpad) {
        u32 |= kAirPlayDisplayFeatures_Touchpad;
    }

    // Full screen 1920x720 @ (0x0)
    activeAreas[0].width = 1920;
    activeAreas[0].height = 720;
    activeAreas[0].originX = 0;
    activeAreas[0].originY = 0;
    activeAreas[0].mainScreenProperties.viewAreaTransitionControl = false;
    activeAreas[0].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
    activeAreas[0].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
    activeAreas[0].mainScreenProperties.drawUIOutsideSafeArea = false;

    // Left screen 1400x720 @ (0,0)
    activeAreas[1].width = 1400;
    activeAreas[1].height = 720;
    activeAreas[1].originX = 0;
    activeAreas[1].originY = 0;
    activeAreas[1].mainScreenProperties.viewAreaTransitionControl = false;
    activeAreas[1].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
    activeAreas[1].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
    activeAreas[1].mainScreenProperties.drawUIOutsideSafeArea = false;

    // Right screen 1400x720 @ (520,0)
    activeAreas[2].width = 1400;
    activeAreas[2].height = 720;
    activeAreas[2].originX = 520;
    activeAreas[2].originY = 0;
    activeAreas[2].mainScreenProperties.viewAreaTransitionControl = false;
    activeAreas[2].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
    activeAreas[2].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
    activeAreas[2].mainScreenProperties.drawUIOutsideSafeArea = false;

    // Middle screen 1400x600 @ (140,60)
    activeAreas[3].width = 1400;
    activeAreas[3].height = 600;
    activeAreas[3].originX = 260;
    activeAreas[3].originY = 60;
    activeAreas[3].mainScreenProperties.viewAreaTransitionControl = false;
    activeAreas[3].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
    activeAreas[3].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
    activeAreas[3].mainScreenProperties.drawUIOutsideSafeArea = false;

    // Initial view area = activeArea[0]
    initialViewArea = 0;

    // Possible transitions to/from the initial view area activeArea[0]
    // activeArea[0] <-> activeArea[1]
    // activeArea[0] <-> activeArea[3]
    adjacentAreas[0] = 1;
    adjacentAreas[1] = 3;
    adjacentAreaCount = 2;

    // Create Main Screen Dictionary
    err = AirPlayMainScreenDictCreate(&screenDict, kMainScreenUUID, u32, gPrimaryInputDevice, mainFPS, mainWidthPixels,
                                      mainHeightPixels, mainWidthMM, mainHeightMM);
    require_noerr(err, exit);

    // Add View Areas
//   err = AirPlayScreenDictSetViewAreas( screenDict, initialViewArea, adjacentAreaCount, adjacentAreas, viewAreaCount, activeAreas, NULL );
//   require_noerr( err, exit );

    // Add Appearance options
    err = AirPlayScreenDictAddUIAppearance(screenDict, gAppearanceMode, gAppearanceSetting);
    require_noerr(err, exit);

    // err = AirPlayScreenDictAddMapAppearance( screenDict, kAirPlayAppearanceMode_Light, kAirPlayAppearanceSetting_Automatic );
    // require_noerr( err, exit );
    app_ulog(kLogLevelNotice, "screenDict = %@", screenDict);
    err = AirPlayInfoArrayAddScreen(&dictArray, screenDict);
    require_noerr(err, exit);
    CFRelease(screenDict);
    screenDict = NULL;

    // Instrument Cluster (show the map upon connect)
    // err = AirPlayAltScreenDictCreate( &screenDict, kAltScreenUUID, altFPS, altWidthPixels, altHeightPixels, altWidthMM, altHeightMM, CFSTR( kAirPlayKey_ScreenURLsInstrumentClusterMap ) );
    // require_noerr( err, exit );

    // Add Appearance option
    // err = AirPlayScreenDictAddMapAppearance( screenDict, kAirPlayAppearanceMode_Dark, kAirPlayAppearanceSetting_Always );
    // require_noerr( err, exit );

    // err = AirPlayInfoArrayAddScreen( &dictArray, screenDict );
    // require_noerr( err, exit );
    // CFRelease( screenDict );
    // screenDict = NULL;

    // Instrument Cluster (show the turn card upon connect)
    // err = AirPlayAltScreenDictCreate( &screenDict, kAltScreen2UUID, altFPS, altWidthPixels, altHeightPixels, altWidthMM, altHeightMM, CFSTR( kAirPlayKey_ScreenURLsInstrumentClusterInstructionCard ) );
    // require_noerr( err, exit );

    // Add Appearance option
    // err = AirPlayScreenDictAddMapAppearance( screenDict, kAirPlayAppearanceMode_Dark, kAirPlayAppearanceSetting_Always );
    // require_noerr( err, exit );

    // err = AirPlayInfoArrayAddScreen( &dictArray, screenDict );
    // require_noerr( err, exit );
    // CFRelease( screenDict );
    // screenDict = NULL;

exit:
    *outErr = err;
    CFReleaseNullSafe(screenDict);
    return (dictArray);
}

static void viewAreaChangeFullToLeft() {
    uint32_t viewAreaIndex = 1;
    uint32_t adjacentAreas[2] = { 0, 2 };
    uint32_t adjacentAreaCount = 2;

    AirPlayReceiverSessionViewAreaUpdate(gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount,
                                         adjacentAreas, NULL, NULL);
}

static void viewAreaChangeLeftToRight() {
    uint32_t viewAreaIndex = 2;
    uint32_t adjacentAreas[2] = { 1, 3 };
    uint32_t adjacentAreaCount = 2;

    AirPlayReceiverSessionViewAreaUpdate(gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount,
                                         adjacentAreas, NULL, NULL);
}

static void viewAreaChangeRightToMiddle() {
    uint32_t viewAreaIndex = 3;
    uint32_t adjacentAreas[2] = { 2, 0 };
    uint32_t adjacentAreaCount = 2;

    AirPlayReceiverSessionViewAreaUpdate(gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount,
                                         adjacentAreas, NULL, NULL);
}

static void viewAreaChangeMiddleToFull() {
    uint32_t viewAreaIndex = 0;
    uint32_t adjacentAreas[2] = { 3, 1 };
    uint32_t adjacentAreaCount = 2;

    AirPlayReceiverSessionViewAreaUpdate(gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount,
                                         adjacentAreas, NULL, NULL);
}

//===========================================================================================================================
//  _getScreenDisplaysWindowConfig
//===========================================================================================================================

static CFArrayRef _getScreenDisplaysWindowConfig(OSStatus* outErr) {
    OSStatus err;

    uint32_t mainWidthPixels = 800;
    uint32_t mainHeightPixels = 1280;
    uint32_t mainWidthMM = 168;
    uint32_t mainHeightMM = 240;
    uint32_t mainFPS = 30;

    CFArrayRef dictArray = NULL;
    CFMutableDictionaryRef screenDict = NULL;
    uint32_t u32 = 0;

    uint32_t viewAreaCount = 2;
    uint32_t initialViewArea = 0;
    AirPlayScreenArea activeAreas[2];
    uint32_t adjacentAreas[1];
    uint32_t adjacentAreaCount;

    if (gKnob) {
        u32 |= kAirPlayDisplayFeatures_Knobs;
    }
    if (gHiFiTouch)   {
        u32 |= kAirPlayDisplayFeatures_HighFidelityTouch;
    }
    if (gLoFiTouch)   {
        u32 |= kAirPlayDisplayFeatures_LowFidelityTouch;
    }
    if (gTouchpad) {
        u32 |= kAirPlayDisplayFeatures_Touchpad;
    }

    // Full screen 800x1200 @ (0,80)
    activeAreas[0].width = 800;
    activeAreas[0].height = 1200;
    activeAreas[0].originX = 0;
    activeAreas[0].originY = 80;
    activeAreas[0].mainScreenProperties.viewAreaTransitionControl = true;
    activeAreas[0].mainScreenProperties.viewAreaSupportsFocusTransfer = true;
    activeAreas[0].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Bottom;
    activeAreas[0].mainScreenProperties.drawUIOutsideSafeArea = false;

    // Left screen 800x480 @ (0,80)
    activeAreas[1].width = 800;
    activeAreas[1].height = 480;
    activeAreas[1].originX = 0;
    activeAreas[1].originY = 80;
    activeAreas[1].mainScreenProperties.viewAreaTransitionControl = true;
    activeAreas[1].mainScreenProperties.viewAreaSupportsFocusTransfer = true;
    activeAreas[1].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_DriveSide;
    activeAreas[1].mainScreenProperties.drawUIOutsideSafeArea = false;

    // Initial view area = activeArea[0]
    initialViewArea = 0;

    // Possible transitions to/from the initial view area activeArea[0]
    // activeArea[0] <-> activeArea[1]
    adjacentAreaCount = 1;
    adjacentAreas[0] = 1;

    // Create Main Screen Dictionary
    err = AirPlayMainScreenDictCreate(&screenDict, kMainScreenUUID, u32, gPrimaryInputDevice, mainFPS, mainWidthPixels,
                                      mainHeightPixels, mainWidthMM, mainHeightMM);
    require_noerr(err, exit);

    // Add View Areas
    err = AirPlayScreenDictSetViewAreas(screenDict, initialViewArea, adjacentAreaCount, adjacentAreas, viewAreaCount,
                                        activeAreas, NULL);
    require_noerr(err, exit);

    // Support Corner Clipping Masks
    err = AirPlayScreenDictSetCornerMasksSupport(screenDict, true);
    require_noerr(err, exit);

    // Since this viewArea supports Focus Transfer, give controller focus at connect
    err = AirPlayScreenDictSetAccessoryGiveFocus(screenDict, false);

    err = AirPlayInfoArrayAddScreen(&dictArray, screenDict);
    require_noerr(err, exit);
    CFRelease(screenDict);
    screenDict = NULL;

exit:
    *outErr = err;
    CFReleaseNullSafe(screenDict);
    return (dictArray);
}

static CFDictionaryRef createInitialModesForBackupCamera() {
    AirPlayModeChanges initialModes;
    AirPlayInitialPermanentEntity initialPerm;
    CFDictionaryRef dict;

    // Example for backup camera where the permanent entity is accessory
    AirPlayModeChangesInit(&initialModes);

    initialModes.speech = kAirPlaySpeechMode_None;
    initialModes.phoneCall = kAirPlayTriState_False;
    initialModes.turnByTurn = kAirPlayTriState_False;

    initialModes.screen.type = kAirPlayTransferType_Borrow;
    initialModes.screen.priority = kAirPlayTransferPriority_UserInitiated;
    initialModes.screen.takeConstraint = kAirPlayConstraint_NotApplicable;
    initialModes.screen.borrowOrUnborrowConstraint = kAirPlayConstraint_Never;
    initialModes.screen.borrowID = kAirPlayBorrowID_BackupCamera;

    initialModes.mainAudio.type = kAirPlayTransferType_Take;
    initialModes.mainAudio.priority = kAirPlayTransferPriority_UserInitiated;
    initialModes.mainAudio.takeConstraint = kAirPlayConstraint_UserInitiated;
    initialModes.mainAudio.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

    AirPlayInitialPermanentEntityInit(&initialPerm);

    initialPerm.permanentEntityScreen = kAirPlayEntity_Accessory;
    initialPerm.screen.takeConstraint = kAirPlayConstraint_UserInitiated;
    initialPerm.screen.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

    initialPerm.permanentEntityMainAudio = kAirPlayEntity_Accessory;
    initialPerm.mainAudio.takeConstraint = kAirPlayConstraint_UserInitiated;
    initialPerm.mainAudio.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

    dict = AirPlayCreateInitialModesDictionary(&initialPerm, &initialModes, CFSTR("Initial modes"), NULL);

    return dict;
}

static CFDictionaryRef createInitialModesForController(void) {
    AirPlayModeChanges initialModes;
    AirPlayInitialPermanentEntity initialPerm;
    CFDictionaryRef dict;

    // Example for controller to have the resources
    AirPlayModeChangesInit(&initialModes);

    initialModes.speech = kAirPlaySpeechMode_None;
    initialModes.phoneCall = kAirPlayTriState_False;
    initialModes.turnByTurn = kAirPlayTriState_False;

    // For backwards compatability
    initialModes.screen.type = kAirPlayTransferType_Take;
    initialModes.screen.priority = kAirPlayTransferPriority_NiceToHave;
    initialModes.screen.takeConstraint = kAirPlayConstraint_Anytime;
    initialModes.screen.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

    initialModes.mainAudio.type = kAirPlayTransferType_Take;
    initialModes.mainAudio.priority = kAirPlayTransferPriority_NiceToHave;
    initialModes.mainAudio.takeConstraint = kAirPlayConstraint_Anytime;
    initialModes.mainAudio.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

    AirPlayInitialPermanentEntityInit(&initialPerm);

    initialPerm.permanentEntityScreen = kAirPlayEntity_Controller;
    initialPerm.permanentEntityMainAudio = kAirPlayEntity_Controller;

    dict = AirPlayCreateInitialModesDictionary(&initialPerm, &initialModes, CFSTR("Initial modes"), NULL);

    return dict;
}

